diff --git a/Cargo.lock b/Cargo.lock index 3023c65ccfb4..097da802f8af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2683,7 +2683,7 @@ dependencies = [ "sp-trie", "sp-version", "static_assertions", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", "tiny-keccak 1.5.0", ] @@ -4729,7 +4729,6 @@ dependencies = [ "nix 0.17.0", "parity-util-mem", "polkadot-cli", - "polkadot-collator", "polkadot-service", "tempfile", ] @@ -4785,32 +4784,6 @@ dependencies = [ "streamunordered", ] -[[package]] -name = "polkadot-availability-store" -version = "0.8.22" -dependencies = [ - "derive_more 0.99.9", - "exit-future", - "futures 0.3.5", - "kvdb", - "kvdb-memorydb", - "kvdb-rocksdb", - "log 0.4.11", - "parity-scale-codec", - "parking_lot 0.9.0", - "polkadot-erasure-coding", - "polkadot-primitives", - "sc-client-api", - "sc-keystore", - "sc-network", - "sp-api", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-runtime", - "tokio 0.2.21", -] - [[package]] name = "polkadot-cli" version = "0.8.22" @@ -4837,34 +4810,6 @@ dependencies = [ "wasm-bindgen-futures", ] -[[package]] -name = "polkadot-collator" -version = "0.8.22" -dependencies = [ - "futures 0.3.5", - "futures-timer 2.0.2", - "log 0.4.11", - "parity-scale-codec", - "polkadot-cli", - "polkadot-network", - "polkadot-primitives", - "polkadot-service", - "polkadot-service-new", - "polkadot-validation", - "sc-cli", - "sc-client-api", - "sc-executor", - "sc-network", - "sc-service", - "sp-api", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-keyring", - "sp-runtime", - "tokio 0.2.21", -] - [[package]] name = "polkadot-core-primitives" version = "0.7.30" @@ -4887,36 +4832,6 @@ dependencies = [ "sp-trie", ] -[[package]] -name = "polkadot-network" -version = "0.8.22" -dependencies = [ - "arrayvec 0.4.12", - "bytes 0.5.5", - "derive_more 0.14.1", - "exit-future", - "futures 0.3.5", - "futures-timer 2.0.2", - "log 0.4.11", - "parity-scale-codec", - "parking_lot 0.9.0", - "polkadot-availability-store", - "polkadot-erasure-coding", - "polkadot-primitives", - "polkadot-validation", - "rand 0.7.3", - "sc-network", - "sc-network-gossip", - "sp-api", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-keyring", - "sp-runtime", - "sp-state-machine", - "wasm-timer", -] - [[package]] name = "polkadot-network-bridge" version = "0.1.0" @@ -4938,27 +4853,6 @@ dependencies = [ "streamunordered", ] -[[package]] -name = "polkadot-network-test" -version = "0.8.22" -dependencies = [ - "futures 0.3.5", - "log 0.4.11", - "parking_lot 0.10.2", - "polkadot-test-runtime-client", - "rand 0.7.3", - "sc-block-builder", - "sc-client-api", - "sc-consensus", - "sc-network", - "sc-network-test", - "sc-service", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-runtime", -] - [[package]] name = "polkadot-node-collation-generation" version = "0.1.0" @@ -5396,7 +5290,7 @@ dependencies = [ "sp-trie", "sp-version", "static_assertions", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", "tiny-keccak 1.5.0", "trie-db", ] @@ -5508,8 +5402,6 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "parking_lot 0.9.0", - "polkadot-availability-store", - "polkadot-network", "polkadot-primitives", "polkadot-rpc", "polkadot-runtime", @@ -5568,7 +5460,6 @@ dependencies = [ "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "parking_lot 0.9.0", - "polkadot-network", "polkadot-node-core-proposer", "polkadot-node-subsystem", "polkadot-overseer", @@ -5699,7 +5590,7 @@ dependencies = [ "sp-transaction-pool", "sp-trie", "sp-version", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", "tiny-keccak 1.5.0", ] @@ -5739,14 +5630,11 @@ dependencies = [ "pallet-balances", "pallet-staking", "pallet-transaction-payment", - "polkadot-availability-store", - "polkadot-network", "polkadot-primitives", "polkadot-rpc", "polkadot-runtime-common", "polkadot-service", "polkadot-test-runtime", - "polkadot-validation", "rand 0.7.3", "sc-authority-discovery", "sc-chain-spec", @@ -5780,25 +5668,16 @@ dependencies = [ name = "polkadot-validation" version = "0.8.22" dependencies = [ - "ansi_term 0.12.1", - "bitvec", "derive_more 0.14.1", - "exit-future", "futures 0.3.5", - "futures-timer 2.0.2", "log 0.4.11", "parity-scale-codec", - "parking_lot 0.9.0", - "polkadot-availability-store", - "polkadot-erasure-coding", "polkadot-parachain", "polkadot-primitives", - "polkadot-statement-table", "sc-basic-authorship", "sc-block-builder", "sc-client-api", "sc-finality-grandpa", - "sc-keystore", "sp-api", "sp-blockchain", "sp-consensus", @@ -5811,7 +5690,6 @@ dependencies = [ "sp-transaction-pool", "sp-trie", "substrate-prometheus-endpoint", - "tokio 0.2.21", ] [[package]] @@ -6515,7 +6393,7 @@ dependencies = [ "sp-std", "sp-transaction-pool", "sp-version", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", ] [[package]] @@ -7238,33 +7116,6 @@ dependencies = [ "wasm-timer", ] -[[package]] -name = "sc-network-test" -version = "0.8.0-rc6" -source = "git+https://github.com/paritytech/substrate#dfe2871b272d2bb343c8fb2b1f0bb671324e52e9" -dependencies = [ - "env_logger", - "futures 0.3.5", - "futures-timer 3.0.2", - "libp2p", - "log 0.4.11", - "parking_lot 0.10.2", - "rand 0.7.3", - "sc-block-builder", - "sc-client-api", - "sc-consensus", - "sc-network", - "sc-service", - "sp-blockchain", - "sp-consensus", - "sp-consensus-babe", - "sp-core", - "sp-runtime", - "substrate-test-runtime", - "substrate-test-runtime-client", - "tempfile", -] - [[package]] name = "sc-offchain" version = "2.0.0-rc6" @@ -8135,20 +7986,6 @@ dependencies = [ "wasm-timer", ] -[[package]] -name = "sp-consensus-aura" -version = "0.8.0-rc6" -source = "git+https://github.com/paritytech/substrate#dfe2871b272d2bb343c8fb2b1f0bb671324e52e9" -dependencies = [ - "parity-scale-codec", - "sp-api", - "sp-application-crypto", - "sp-inherents", - "sp-runtime", - "sp-std", - "sp-timestamp", -] - [[package]] name = "sp-consensus-babe" version = "0.8.0-rc6" @@ -8819,67 +8656,6 @@ dependencies = [ "sp-state-machine", ] -[[package]] -name = "substrate-test-runtime" -version = "2.0.0-rc6" -source = "git+https://github.com/paritytech/substrate#dfe2871b272d2bb343c8fb2b1f0bb671324e52e9" -dependencies = [ - "cfg-if", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-rpc-runtime-api", - "log 0.4.11", - "memory-db", - "pallet-babe", - "pallet-timestamp", - "parity-scale-codec", - "parity-util-mem", - "sc-service", - "serde", - "sp-api", - "sp-application-crypto", - "sp-block-builder", - "sp-consensus-aura", - "sp-consensus-babe", - "sp-core", - "sp-finality-grandpa", - "sp-inherents", - "sp-io", - "sp-keyring", - "sp-offchain", - "sp-runtime", - "sp-runtime-interface", - "sp-session", - "sp-std", - "sp-transaction-pool", - "sp-trie", - "sp-version", - "substrate-wasm-builder-runner 1.0.6 (git+https://github.com/paritytech/substrate)", - "trie-db", -] - -[[package]] -name = "substrate-test-runtime-client" -version = "2.0.0-rc6" -source = "git+https://github.com/paritytech/substrate#dfe2871b272d2bb343c8fb2b1f0bb671324e52e9" -dependencies = [ - "futures 0.3.5", - "parity-scale-codec", - "sc-block-builder", - "sc-client-api", - "sc-consensus", - "sc-light", - "sc-service", - "sp-api", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-runtime", - "substrate-test-client", - "substrate-test-runtime", -] - [[package]] name = "substrate-test-utils" version = "2.0.0-rc6" @@ -8900,11 +8676,6 @@ dependencies = [ "syn 1.0.33", ] -[[package]] -name = "substrate-wasm-builder-runner" -version = "1.0.6" -source = "git+https://github.com/paritytech/substrate#dfe2871b272d2bb343c8fb2b1f0bb671324e52e9" - [[package]] name = "substrate-wasm-builder-runner" version = "1.0.6" @@ -9012,33 +8783,15 @@ dependencies = [ "polkadot-parachain", "sp-io", "sp-std", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", "tiny-keccak 1.5.0", ] -[[package]] -name = "test-parachain-adder-collator" -version = "0.1.0" -dependencies = [ - "futures 0.3.5", - "parity-scale-codec", - "parking_lot 0.10.2", - "polkadot-collator", - "polkadot-parachain", - "polkadot-primitives", - "polkadot-service", - "sc-client-api", - "sp-api", - "sp-core", - "sp-runtime", - "test-parachain-adder", -] - [[package]] name = "test-parachain-halt" version = "0.8.22" dependencies = [ - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", ] [[package]] @@ -10209,7 +9962,7 @@ dependencies = [ "sp-trie", "sp-version", "static_assertions", - "substrate-wasm-builder-runner 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "substrate-wasm-builder-runner", "tiny-keccak 1.5.0", ] diff --git a/Cargo.toml b/Cargo.toml index 817851211fbd..401544ad6c49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,8 +10,6 @@ edition = "2018" [dependencies] cli = { package = "polkadot-cli", path = "cli" } -# It looks like this is the only way to pass features to it -collator = { package = "polkadot-collator", path = "collator" } futures = "0.3.4" service = { package = "polkadot-service", path = "service" } parity-util-mem = { version = "*", default-features = false, features = ["jemalloc-global"] } @@ -23,13 +21,9 @@ tempfile = "3.1.0" [workspace] members = [ - "availability-store", "cli", - "collator", "core-primitives", "erasure-coding", - "network", - "network/test", "primitives", "runtime/common", "runtime/parachains", @@ -69,7 +63,6 @@ members = [ "parachain/test-parachains", "parachain/test-parachains/adder", - "parachain/test-parachains/adder/collator", ] [badges] @@ -83,5 +76,4 @@ panic = "unwind" runtime-benchmarks=["cli/runtime-benchmarks"] service-rewr= [ "cli/service-rewr", - "collator/service-rewr", ] diff --git a/availability-store/Cargo.toml b/availability-store/Cargo.toml deleted file mode 100644 index 82c630ac6352..000000000000 --- a/availability-store/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "polkadot-availability-store" -description = "Persistent database for parachain data" -version = "0.8.22" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -polkadot-primitives = { path = "../primitives" } -polkadot-erasure-coding = { path = "../erasure-coding" } -parking_lot = "0.9.0" -derive_more = "0.99" -log = "0.4.8" -futures = "0.3.4" -tokio = { version = "0.2.13", features = ["rt-core"] } -exit-future = "0.2.0" -codec = { package = "parity-scale-codec", version = "1.3.4", features = ["derive"] } -sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } -consensus_common = { package = "sp-consensus", git = "https://github.com/paritytech/substrate", branch = "master" } -client = { package = "sc-client-api", git = "https://github.com/paritytech/substrate", branch = "master", version = "2.0.0-rc5" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -keystore = { package = "sc-keystore", git = "https://github.com/paritytech/substrate", branch = "master" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -kvdb = "0.7.0" -kvdb-memorydb = "0.7.0" - -[target.'cfg(not(target_os = "unknown"))'.dependencies] -kvdb-rocksdb = "0.9.0" diff --git a/availability-store/src/lib.rs b/availability-store/src/lib.rs deleted file mode 100644 index 4b973c7d0271..000000000000 --- a/availability-store/src/lib.rs +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018-2020 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 . - -//! Persistent database for parachain data: PoV block data, erasure-coding chunks and outgoing messages. -//! -//! This will be written into during the block validation pipeline, and queried -//! by networking code in order to circulate required data and maintain availability -//! of it. - -#![warn(missing_docs)] - -use futures::prelude::*; -use futures::channel::{mpsc, oneshot}; -use keystore::KeyStorePtr; -use polkadot_primitives::v0::{ - Hash, Block, - PoVBlock, AbridgedCandidateReceipt, ErasureChunk, - ParachainHost, AvailableData, OmittedValidationData, -}; -use sp_runtime::traits::HashFor; -use sp_blockchain::Result as ClientResult; -use client::{ - BlockchainEvents, BlockBackend, -}; -use sp_api::{ApiExt, ProvideRuntimeApi}; -use codec::{Encode, Decode}; -use sp_core::traits::SpawnNamed; - -use log::warn; - -use std::sync::Arc; -use std::collections::HashSet; -use std::path::PathBuf; -use std::io; -use std::pin::Pin; - -mod worker; -mod store; - -pub use worker::AvailabilityBlockImport; -pub use store::AwaitedFrontierEntry; - -use worker::{ - Worker, WorkerHandle, IncludedParachainBlocks, WorkerMsg, MakeAvailable, Chunks -}; - -use store::Store as InnerStore; - -const LOG_TARGET: &str = "availability"; - -/// Configuration for the availability store. -pub struct Config { - /// Cache size in bytes. If `None` default is used. - pub cache_size: Option, - /// Path to the database. - pub path: PathBuf, -} - -/// An abstraction around networking for the availablity-store. -/// -/// Currently it is not possible to use the networking code in the availability store -/// core directly due to a number of loop dependencies it requires: -/// -/// `availability-store` -> `network` -> `availability-store` -/// -/// `availability-store` -> `network` -> `validation` -> `availability-store` -/// -/// So we provide this trait that gets implemented for a type in -/// the [`network`] module or a mock in tests. -/// -/// [`network`]: ../polkadot_network/index.html -pub trait ErasureNetworking { - /// Errors that can occur when fetching erasure chunks. - type Error: std::fmt::Debug + 'static; - - /// Fetch an erasure chunk from the networking service. - fn fetch_erasure_chunk( - &self, - candidate_hash: &Hash, - index: u32, - ) -> Pin> + Send>>; - - /// Distributes an erasure chunk to the correct validator node. - fn distribute_erasure_chunk( - &self, - candidate_hash: Hash, - chunk: ErasureChunk, - ); -} - -/// Data that, when combined with an `AbridgedCandidateReceipt`, is enough -/// to fully re-execute a block. -#[derive(Debug, Encode, Decode, PartialEq)] -pub struct ExecutionData { - /// The `PoVBlock`. - pub pov_block: PoVBlock, - /// The data omitted from the `AbridgedCandidateReceipt`. - pub omitted_validation: OmittedValidationData, -} - -/// Handle to the availability store. -/// -/// This provides a proxying API that -/// * in case of write operations provides async methods that send data to -/// the background worker and resolve when that data is processed by the worker -/// * in case of read opeartions queries the underlying storage synchronously. -#[derive(Clone)] -pub struct Store { - inner: InnerStore, - worker: Arc, - to_worker: mpsc::UnboundedSender, -} - -impl Store { - /// Create a new `Store` with given config on disk. - /// - /// Creating a store among other things starts a background worker thread that - /// handles most of the write operations to the storage. - #[cfg(not(target_os = "unknown"))] - pub fn new(config: Config, network: EN) -> io::Result - where EN: ErasureNetworking + Send + Sync + Clone + 'static - { - let inner = InnerStore::new(config)?; - let worker = Arc::new(Worker::start(inner.clone(), network)); - let to_worker = worker.to_worker().clone(); - - Ok(Self { - inner, - worker, - to_worker, - }) - } - - /// Create a new in-memory `Store`. Useful for tests. - /// - /// Creating a store among other things starts a background worker thread - /// that handles most of the write operations to the storage. - pub fn new_in_memory(network: EN) -> Self - where EN: ErasureNetworking + Send + Sync + Clone + 'static - { - let inner = InnerStore::new_in_memory(); - let worker = Arc::new(Worker::start(inner.clone(), network)); - let to_worker = worker.to_worker().clone(); - - Self { - inner, - worker, - to_worker, - } - } - - /// Obtain a [`BlockImport`] implementation to import blocks into this store. - /// - /// This block import will act upon all newly imported blocks sending information - /// about parachain heads included in them to this `Store`'s background worker. - /// The user may create multiple instances of [`BlockImport`]s with this call. - /// - /// [`BlockImport`]: https://substrate.dev/rustdocs/v1.0/substrate_consensus_common/trait.BlockImport.html - pub fn block_import( - &self, - wrapped_block_import: I, - client: Arc

, - spawner: impl SpawnNamed, - keystore: KeyStorePtr, - ) -> ClientResult> - where - P: ProvideRuntimeApi + BlockchainEvents + BlockBackend + Send + Sync + 'static, - P::Api: ParachainHost, - P::Api: ApiExt, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, - { - let to_worker = self.to_worker.clone(); - - let import = AvailabilityBlockImport::new( - client, - wrapped_block_import, - spawner, - keystore, - to_worker, - ); - - Ok(import) - } - - /// Make some data available provisionally. - /// - /// Validators with the responsibility of maintaining availability - /// for a block or collators collating a block will call this function - /// in order to persist that data to disk and so it can be queried and provided - /// to other nodes in the network. - /// - /// Determination of invalidity is beyond the scope of this function. - /// - /// This method will send the data to the background worker, allowing the caller to - /// asynchronously wait for the result. - pub async fn make_available(&self, candidate_hash: Hash, available_data: AvailableData) - -> io::Result<()> - { - let (s, r) = oneshot::channel(); - let msg = WorkerMsg::MakeAvailable(MakeAvailable { - candidate_hash, - available_data, - result: s, - }); - - let _ = self.to_worker.unbounded_send(msg); - - if let Ok(Ok(())) = r.await { - Ok(()) - } else { - Err(io::Error::new(io::ErrorKind::Other, format!("adding erasure chunks failed"))) - } - - } - - /// Get a set of all chunks we are waiting for. - pub fn awaited_chunks(&self) -> Option> { - self.inner.awaited_chunks() - } - - /// Adds an erasure chunk to storage. - /// - /// The chunk should be checked for validity against the root of encoding - /// and its proof prior to calling this. - /// - /// This method will send the chunk to the background worker, allowing the caller to - /// asynchronously wait for the result. - pub async fn add_erasure_chunk( - &self, - candidate: AbridgedCandidateReceipt, - n_validators: u32, - chunk: ErasureChunk, - ) -> io::Result<()> { - self.add_erasure_chunks(candidate, n_validators, std::iter::once(chunk)).await - } - - /// Adds a set of erasure chunks to storage. - /// - /// The chunks should be checked for validity against the root of encoding - /// and its proof prior to calling this. - /// - /// This method will send the chunks to the background worker, allowing the caller to - /// asynchronously wait for the result. - pub async fn add_erasure_chunks( - &self, - candidate: AbridgedCandidateReceipt, - n_validators: u32, - chunks: I, - ) -> io::Result<()> - where I: IntoIterator - { - let candidate_hash = candidate.hash(); - - self.add_candidate(candidate).await?; - - let (s, r) = oneshot::channel(); - let chunks = chunks.into_iter().collect(); - - let msg = WorkerMsg::Chunks(Chunks { - candidate_hash, - chunks, - n_validators, - result: s, - }); - - let _ = self.to_worker.unbounded_send(msg); - - if let Ok(Ok(())) = r.await { - Ok(()) - } else { - Err(io::Error::new(io::ErrorKind::Other, format!("adding erasure chunks failed"))) - } - } - - /// Queries an erasure chunk by the candidate hash and validator index. - pub fn get_erasure_chunk( - &self, - candidate_hash: &Hash, - validator_index: usize, - ) -> Option { - self.inner.get_erasure_chunk(candidate_hash, validator_index) - } - - /// Note a validator's index and a number of validators at a relay parent in the - /// store. - /// - /// This should be done before adding erasure chunks with this relay parent. - pub fn note_validator_index_and_n_validators( - &self, - relay_parent: &Hash, - validator_index: u32, - n_validators: u32, - ) -> io::Result<()> { - self.inner.note_validator_index_and_n_validators( - relay_parent, - validator_index, - n_validators, - ) - } - - // Stores a candidate receipt. - async fn add_candidate( - &self, - candidate: AbridgedCandidateReceipt, - ) -> io::Result<()> { - let (s, r) = oneshot::channel(); - - let msg = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { - blocks: vec![crate::worker::IncludedParachainBlock { - candidate, - available_data: None, - }], - result: s, - }); - - let _ = self.to_worker.unbounded_send(msg); - - if let Ok(Ok(())) = r.await { - Ok(()) - } else { - Err(io::Error::new(io::ErrorKind::Other, format!("adding erasure chunks failed"))) - } - } - - /// Queries a candidate receipt by its hash. - pub fn get_candidate(&self, candidate_hash: &Hash) - -> Option - { - self.inner.get_candidate(candidate_hash) - } - - /// Query execution data by pov-block hash. - pub fn execution_data(&self, candidate_hash: &Hash) - -> Option - { - self.inner.execution_data(candidate_hash) - } -} diff --git a/availability-store/src/store.rs b/availability-store/src/store.rs deleted file mode 100644 index cd76de5d44ad..000000000000 --- a/availability-store/src/store.rs +++ /dev/null @@ -1,620 +0,0 @@ -// Copyright 2018-2020 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 . - -#[cfg(not(target_os = "unknown"))] -use kvdb_rocksdb::{Database, DatabaseConfig}; -use kvdb::{KeyValueDB, DBTransaction}; -use codec::{Encode, Decode}; -use polkadot_erasure_coding as erasure; -use polkadot_primitives::v0::{ - Hash, ErasureChunk, AvailableData, AbridgedCandidateReceipt, -}; -use parking_lot::Mutex; - -use log::{trace, warn}; -use std::collections::HashSet; -use std::sync::Arc; -use std::iter::FromIterator; -use std::io; - -use crate::{LOG_TARGET, Config, ExecutionData}; - -mod columns { - pub const DATA: u32 = 0; - pub const META: u32 = 1; - pub const NUM_COLUMNS: u32 = 2; -} - -#[derive(Clone)] -pub struct Store { - inner: Arc, - candidate_descendents_lock: Arc> -} - -// data keys -fn execution_data_key(candidate_hash: &Hash) -> Vec { - (candidate_hash, 0i8).encode() -} - -fn erasure_chunks_key(candidate_hash: &Hash) -> Vec { - (candidate_hash, 1i8).encode() -} - -fn candidate_key(candidate_hash: &Hash) -> Vec { - (candidate_hash, 2i8).encode() -} - -fn candidates_with_relay_parent_key(relay_block: &Hash) -> Vec { - (relay_block, 4i8).encode() -} - -// meta keys -const AWAITED_CHUNKS_KEY: [u8; 14] = *b"awaited_chunks"; - -fn validator_index_and_n_validators_key(relay_parent: &Hash) -> Vec { - (relay_parent, 1i8).encode() -} - -fn available_chunks_key(candidate_hash: &Hash) -> Vec { - (candidate_hash, 2i8).encode() -} - -/// An entry in the awaited frontier of chunks we are interested in. -#[derive(Encode, Decode, Debug, Hash, PartialEq, Eq, Clone)] -pub struct AwaitedFrontierEntry { - /// The hash of the candidate for which we want to fetch a chunk for. - /// There will be duplicate entries in the case of multiple candidates with - /// the same erasure-root, but this is unlikely. - pub candidate_hash: Hash, - /// Although the relay-parent is implicitly referenced by the candidate hash, - /// we include it here as well for convenience in pruning the set. - pub relay_parent: Hash, - /// The index of the validator we represent. - pub validator_index: u32, -} - -impl Store { - /// Create a new `Store` with given condig on disk. - #[cfg(not(target_os = "unknown"))] - pub(super) fn new(config: Config) -> io::Result { - let mut db_config = DatabaseConfig::with_columns(columns::NUM_COLUMNS); - - if let Some(cache_size) = config.cache_size { - let mut memory_budget = std::collections::HashMap::new(); - for i in 0..columns::NUM_COLUMNS { - memory_budget.insert(i, cache_size / columns::NUM_COLUMNS as usize); - } - - db_config.memory_budget = memory_budget; - } - - let path = config.path.to_str().ok_or_else(|| io::Error::new( - io::ErrorKind::Other, - format!("Bad database path: {:?}", config.path), - ))?; - - let db = Database::open(&db_config, &path)?; - - Ok(Store { - inner: Arc::new(db), - candidate_descendents_lock: Arc::new(Mutex::new(())), - }) - } - - /// Create a new `Store` in-memory. Useful for tests. - pub(super) fn new_in_memory() -> Self { - Store { - inner: Arc::new(::kvdb_memorydb::create(columns::NUM_COLUMNS)), - candidate_descendents_lock: Arc::new(Mutex::new(())), - } - } - - /// Make some data available provisionally. - pub(crate) fn make_available(&self, candidate_hash: Hash, available_data: AvailableData) - -> io::Result<()> - { - let mut tx = DBTransaction::new(); - - // at the moment, these structs are identical. later, we will also - // keep outgoing message queues available, and these are not needed - // for execution. - let AvailableData { pov_block, omitted_validation } = available_data; - let execution_data = ExecutionData { - pov_block, - omitted_validation, - }; - - tx.put_vec( - columns::DATA, - execution_data_key(&candidate_hash).as_slice(), - execution_data.encode(), - ); - - self.inner.write(tx) - } - - /// Get a set of all chunks we are waiting for. - pub fn awaited_chunks(&self) -> Option> { - self.query_inner(columns::META, &AWAITED_CHUNKS_KEY).map(|vec: Vec| { - HashSet::from_iter(vec.into_iter()) - }) - } - - /// Adds a set of candidates hashes that were included in a relay block by the block's parent. - /// - /// If we already possess the receipts for these candidates _and_ our position at the specified - /// relay chain the awaited frontier of the erasure chunks will also be extended. - /// - /// This method modifies the erasure chunks awaited frontier by adding this validator's - /// chunks from `candidates` to it. In order to do so the information about this validator's - /// position at parent `relay_parent` should be known to the store prior to calling this - /// method, in other words `note_validator_index_and_n_validators` should be called for - /// the given `relay_parent` before calling this function. - pub(crate) fn note_candidates_with_relay_parent( - &self, - relay_parent: &Hash, - candidates: &[Hash], - ) -> io::Result<()> { - let mut tx = DBTransaction::new(); - let dbkey = candidates_with_relay_parent_key(relay_parent); - - // This call can race against another call to `note_candidates_with_relay_parent` - // with a different set of descendents. - let _lock = self.candidate_descendents_lock.lock(); - - if let Some((validator_index, _)) = self.get_validator_index_and_n_validators(relay_parent) { - let candidates = candidates.clone(); - let awaited_frontier: Vec = self - .query_inner(columns::META, &AWAITED_CHUNKS_KEY) - .unwrap_or_else(|| Vec::new()); - - let mut awaited_frontier: HashSet = - HashSet::from_iter(awaited_frontier.into_iter()); - - awaited_frontier.extend(candidates.iter().cloned().map(|candidate_hash| { - AwaitedFrontierEntry { - relay_parent: relay_parent.clone(), - candidate_hash, - validator_index, - } - })); - let awaited_frontier = Vec::from_iter(awaited_frontier.into_iter()); - tx.put_vec(columns::META, &AWAITED_CHUNKS_KEY, awaited_frontier.encode()); - } - - let mut descendent_candidates = self.get_candidates_with_relay_parent(relay_parent); - descendent_candidates.extend(candidates.iter().cloned()); - tx.put_vec(columns::DATA, &dbkey, descendent_candidates.encode()); - - self.inner.write(tx) - } - - /// Make a validator's index and a number of validators at a relay parent available. - pub(crate) fn note_validator_index_and_n_validators( - &self, - relay_parent: &Hash, - validator_index: u32, - n_validators: u32, - ) -> io::Result<()> { - let mut tx = DBTransaction::new(); - let dbkey = validator_index_and_n_validators_key(relay_parent); - - tx.put_vec(columns::META, &dbkey, (validator_index, n_validators).encode()); - - self.inner.write(tx) - } - - /// Query a validator's index and n_validators by relay parent. - pub(crate) fn get_validator_index_and_n_validators(&self, relay_parent: &Hash) -> Option<(u32, u32)> { - let dbkey = validator_index_and_n_validators_key(relay_parent); - - self.query_inner(columns::META, &dbkey) - } - - /// Add a set of chunks. - /// - /// The same as `add_erasure_chunk` but adds a set of chunks in one atomic transaction. - pub fn add_erasure_chunks( - &self, - n_validators: u32, - candidate_hash: &Hash, - chunks: I, - ) -> io::Result<()> - where I: IntoIterator - { - if let Some(receipt) = self.get_candidate(candidate_hash) { - let mut tx = DBTransaction::new(); - let dbkey = erasure_chunks_key(candidate_hash); - - let mut v = self.query_inner(columns::DATA, &dbkey).unwrap_or(Vec::new()); - - let av_chunks_key = available_chunks_key(candidate_hash); - let mut have_chunks = self.query_inner(columns::META, &av_chunks_key).unwrap_or(Vec::new()); - - let awaited_frontier: Option> = self.query_inner( - columns::META, - &AWAITED_CHUNKS_KEY, - ); - - for chunk in chunks.into_iter() { - if !have_chunks.contains(&chunk.index) { - have_chunks.push(chunk.index); - } - v.push(chunk); - } - - if let Some(mut awaited_frontier) = awaited_frontier { - awaited_frontier.retain(|entry| { - !( - entry.relay_parent == receipt.relay_parent && - &entry.candidate_hash == candidate_hash && - have_chunks.contains(&entry.validator_index) - ) - }); - tx.put_vec(columns::META, &AWAITED_CHUNKS_KEY, awaited_frontier.encode()); - } - - // If there are no block data in the store at this point, - // check that they can be reconstructed now and add them to store if they can. - if self.execution_data(&candidate_hash).is_none() { - if let Ok(available_data) = erasure::reconstruct_v0( - n_validators as usize, - v.iter().map(|chunk| (chunk.chunk.as_ref(), chunk.index as usize)), - ) - { - self.make_available(*candidate_hash, available_data)?; - } - } - - tx.put_vec(columns::DATA, &dbkey, v.encode()); - tx.put_vec(columns::META, &av_chunks_key, have_chunks.encode()); - - self.inner.write(tx) - } else { - trace!(target: LOG_TARGET, "Candidate with hash {} not found", candidate_hash); - Ok(()) - } - } - - /// Queries an erasure chunk by its block's relay-parent, the candidate hash, and index. - pub fn get_erasure_chunk( - &self, - candidate_hash: &Hash, - index: usize, - ) -> Option { - self.query_inner(columns::DATA, &erasure_chunks_key(candidate_hash)) - .and_then(|chunks: Vec| { - chunks.iter() - .find(|chunk: &&ErasureChunk| chunk.index == index as u32) - .map(|chunk| chunk.clone()) - }) - } - - /// Stores a candidate receipt. - pub fn add_candidate( - &self, - receipt: &AbridgedCandidateReceipt, - ) -> io::Result<()> { - let candidate_hash = receipt.hash(); - let dbkey = candidate_key(&candidate_hash); - let mut tx = DBTransaction::new(); - - tx.put_vec(columns::DATA, &dbkey, receipt.encode()); - - self.inner.write(tx) - } - - /// Queries a candidate receipt by the relay parent hash and its hash. - pub(crate) fn get_candidate(&self, candidate_hash: &Hash) - -> Option - { - self.query_inner(columns::DATA, &candidate_key(candidate_hash)) - } - - /// Note that a set of candidates have been included in a finalized block with given hash and parent hash. - pub(crate) fn candidates_finalized( - &self, - relay_parent: Hash, - finalized_candidates: HashSet, - ) -> io::Result<()> { - let mut tx = DBTransaction::new(); - - let awaited_frontier: Option> = self - .query_inner(columns::META, &AWAITED_CHUNKS_KEY); - - if let Some(mut awaited_frontier) = awaited_frontier { - awaited_frontier.retain(|entry| entry.relay_parent != relay_parent); - tx.put_vec(columns::META, &AWAITED_CHUNKS_KEY, awaited_frontier.encode()); - } - - let candidates = self.get_candidates_with_relay_parent(&relay_parent); - - for candidate in candidates.into_iter().filter(|c| !finalized_candidates.contains(c)) { - // we only delete this data for candidates which were not finalized. - // we keep all data for the finalized chain forever at the moment. - tx.delete(columns::DATA, execution_data_key(&candidate).as_slice()); - tx.delete(columns::DATA, &erasure_chunks_key(&candidate)); - tx.delete(columns::DATA, &candidate_key(&candidate)); - - tx.delete(columns::META, &available_chunks_key(&candidate)); - } - - self.inner.write(tx) - } - - /// Query execution data by relay parent and candidate hash. - pub(crate) fn execution_data(&self, candidate_hash: &Hash) -> Option { - self.query_inner(columns::DATA, &execution_data_key(candidate_hash)) - } - - /// Get candidates which pinned to the environment of the given relay parent. - /// Note that this is not necessarily the same as candidates that were included in a direct - /// descendent of the given relay-parent. - fn get_candidates_with_relay_parent(&self, relay_parent: &Hash) -> Vec { - let key = candidates_with_relay_parent_key(relay_parent); - self.query_inner(columns::DATA, &key[..]).unwrap_or_default() - } - - fn query_inner(&self, column: u32, key: &[u8]) -> Option { - match self.inner.get(column, key) { - Ok(Some(raw)) => { - let res = T::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed"); - Some(res) - } - Ok(None) => None, - Err(e) => { - warn!(target: LOG_TARGET, "Error reading from the availability store: {:?}", e); - None - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use polkadot_erasure_coding::{self as erasure}; - use polkadot_primitives::v0::{ - Id as ParaId, BlockData, AvailableData, PoVBlock, OmittedValidationData, - }; - - fn available_data(block_data: &[u8]) -> AvailableData { - AvailableData { - pov_block: PoVBlock { - block_data: BlockData(block_data.to_vec()), - }, - omitted_validation: OmittedValidationData { - global_validation: Default::default(), - local_validation: Default::default(), - } - } - } - - fn execution_data(available: &AvailableData) -> ExecutionData { - let AvailableData { pov_block, omitted_validation } = available.clone(); - ExecutionData { pov_block, omitted_validation } - } - - #[test] - fn finalization_removes_unneeded() { - let relay_parent = [1; 32].into(); - - let para_id_1 = 5.into(); - let para_id_2 = 6.into(); - - let mut candidate_1 = AbridgedCandidateReceipt::default(); - let mut candidate_2 = AbridgedCandidateReceipt::default(); - - candidate_1.parachain_index = para_id_1; - candidate_1.commitments.erasure_root = [6; 32].into(); - candidate_1.relay_parent = relay_parent; - - candidate_2.parachain_index = para_id_2; - candidate_2.commitments.erasure_root = [6; 32].into(); - candidate_2.relay_parent = relay_parent; - - - let candidate_1_hash = candidate_1.hash(); - let candidate_2_hash = candidate_2.hash(); - - let available_data_1 = available_data(&[1, 2, 3]); - let available_data_2 = available_data(&[4, 5, 6]); - - let erasure_chunk_1 = ErasureChunk { - chunk: vec![10, 20, 30], - index: 1, - proof: vec![], - }; - - let erasure_chunk_2 = ErasureChunk { - chunk: vec![40, 50, 60], - index: 1, - proof: vec![], - }; - - let store = Store::new_in_memory(); - store.make_available(candidate_1_hash, available_data_1.clone()).unwrap(); - - store.make_available(candidate_2_hash, available_data_2.clone()).unwrap(); - - store.add_candidate(&candidate_1).unwrap(); - store.add_candidate(&candidate_2).unwrap(); - - store.note_candidates_with_relay_parent(&relay_parent, &[candidate_1_hash, candidate_2_hash]).unwrap(); - - assert!(store.add_erasure_chunks(3, &candidate_1_hash, vec![erasure_chunk_1.clone()]).is_ok()); - assert!(store.add_erasure_chunks(3, &candidate_2_hash, vec![erasure_chunk_2.clone()]).is_ok()); - - assert_eq!(store.execution_data(&candidate_1_hash).unwrap(), execution_data(&available_data_1)); - assert_eq!(store.execution_data(&candidate_2_hash).unwrap(), execution_data(&available_data_2)); - - assert_eq!(store.get_erasure_chunk(&candidate_1_hash, 1).as_ref(), Some(&erasure_chunk_1)); - assert_eq!(store.get_erasure_chunk(&candidate_2_hash, 1), Some(erasure_chunk_2)); - - assert_eq!(store.get_candidate(&candidate_1_hash), Some(candidate_1.clone())); - assert_eq!(store.get_candidate(&candidate_2_hash), Some(candidate_2.clone())); - - store.candidates_finalized(relay_parent, [candidate_1_hash].iter().cloned().collect()).unwrap(); - - assert_eq!(store.get_erasure_chunk(&candidate_1_hash, 1).as_ref(), Some(&erasure_chunk_1)); - assert!(store.get_erasure_chunk(&candidate_2_hash, 1).is_none()); - - assert_eq!(store.get_candidate(&candidate_1_hash), Some(candidate_1)); - assert_eq!(store.get_candidate(&candidate_2_hash), None); - - assert_eq!(store.execution_data(&candidate_1_hash).unwrap(), execution_data(&available_data_1)); - assert!(store.execution_data(&candidate_2_hash).is_none()); - } - - #[test] - fn erasure_coding() { - let relay_parent: Hash = [1; 32].into(); - let para_id: ParaId = 5.into(); - let available_data = available_data(&[42; 8]); - let n_validators = 5; - - let erasure_chunks = erasure::obtain_chunks_v0( - n_validators, - &available_data, - ).unwrap(); - - let branches = erasure::branches(erasure_chunks.as_ref()); - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.commitments.erasure_root = [6; 32].into(); - candidate.relay_parent = relay_parent; - - let candidate_hash = candidate.hash(); - - let chunks: Vec<_> = erasure_chunks - .iter() - .zip(branches.map(|(proof, _)| proof)) - .enumerate() - .map(|(index, (chunk, proof))| ErasureChunk { - chunk: chunk.clone(), - proof, - index: index as u32, - }) - .collect(); - - let store = Store::new_in_memory(); - - store.add_candidate(&candidate).unwrap(); - store.add_erasure_chunks(n_validators as u32, &candidate_hash, vec![chunks[0].clone()]).unwrap(); - assert_eq!(store.get_erasure_chunk(&candidate_hash, 0), Some(chunks[0].clone())); - - assert!(store.execution_data(&candidate_hash).is_none()); - - store.add_erasure_chunks(n_validators as u32, &candidate_hash, chunks).unwrap(); - assert_eq!(store.execution_data(&candidate_hash), Some(execution_data(&available_data))); - } - - #[test] - fn add_validator_index_works() { - let relay_parent = [42; 32].into(); - let store = Store::new_in_memory(); - - store.note_validator_index_and_n_validators(&relay_parent, 42, 24).unwrap(); - assert_eq!(store.get_validator_index_and_n_validators(&relay_parent).unwrap(), (42, 24)); - } - - #[test] - fn add_candidates_in_relay_block_works() { - let relay_parent = [42; 32].into(); - let store = Store::new_in_memory(); - - let candidates = vec![[1; 32].into(), [2; 32].into(), [3; 32].into()]; - - store.note_candidates_with_relay_parent(&relay_parent, &candidates).unwrap(); - assert_eq!(store.get_candidates_with_relay_parent(&relay_parent), candidates); - } - - #[test] - fn awaited_chunks_works() { - use std::iter::FromIterator; - let validator_index = 3; - let n_validators = 10; - let relay_parent = [42; 32].into(); - let erasure_root_1 = [11; 32].into(); - let erasure_root_2 = [12; 32].into(); - let mut receipt_1 = AbridgedCandidateReceipt::default(); - let mut receipt_2 = AbridgedCandidateReceipt::default(); - - - receipt_1.parachain_index = 1.into(); - receipt_1.commitments.erasure_root = erasure_root_1; - receipt_1.relay_parent = relay_parent; - - receipt_2.parachain_index = 2.into(); - receipt_2.commitments.erasure_root = erasure_root_2; - receipt_2.relay_parent = relay_parent; - - let receipt_1_hash = receipt_1.hash(); - let receipt_2_hash = receipt_2.hash(); - - let chunk = ErasureChunk { - chunk: vec![1, 2, 3], - index: validator_index, - proof: Vec::new(), - }; - let candidates = vec![receipt_1_hash, receipt_2_hash]; - - let store = Store::new_in_memory(); - - store.note_validator_index_and_n_validators( - &relay_parent, - validator_index, - n_validators - ).unwrap(); - store.add_candidate(&receipt_1).unwrap(); - store.add_candidate(&receipt_2).unwrap(); - - // We are waiting for chunks from two candidates. - store.note_candidates_with_relay_parent(&relay_parent, &candidates).unwrap(); - - let awaited_frontier = store.awaited_chunks().unwrap(); - warn!(target: "availability", "awaited {:?}", awaited_frontier); - let expected: HashSet<_> = candidates - .clone() - .into_iter() - .map(|c| AwaitedFrontierEntry { - relay_parent, - candidate_hash: c, - validator_index, - }) - .collect(); - assert_eq!(awaited_frontier, expected); - - // We add chunk from one of the candidates. - store.add_erasure_chunks(n_validators, &receipt_1_hash, vec![chunk]).unwrap(); - - let awaited_frontier = store.awaited_chunks().unwrap(); - // Now we wait for the other chunk that we haven't received yet. - let expected: HashSet<_> = vec![AwaitedFrontierEntry { - relay_parent, - candidate_hash: receipt_2_hash, - validator_index, - }].into_iter().collect(); - - assert_eq!(awaited_frontier, expected); - - // Finalizing removes awaited candidates from frontier. - store.candidates_finalized(relay_parent, HashSet::from_iter(candidates.into_iter())).unwrap(); - - assert_eq!(store.awaited_chunks().unwrap().len(), 0); - } -} diff --git a/availability-store/src/worker.rs b/availability-store/src/worker.rs deleted file mode 100644 index a7cf7ec41dae..000000000000 --- a/availability-store/src/worker.rs +++ /dev/null @@ -1,941 +0,0 @@ -// Copyright 2018-2020 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 . - -use std::collections::{HashMap, HashSet}; -use std::io; -use std::sync::Arc; -use std::thread; - -use log::{error, info, trace, warn}; -use sp_blockchain::Result as ClientResult; -use sp_runtime::traits::{Header as HeaderT, Block as BlockT, HashFor, BlakeTwo256}; -use sp_api::{ApiExt, ProvideRuntimeApi}; -use client::{ - BlockchainEvents, BlockBackend, - blockchain::ProvideCache, -}; -use consensus_common::{ - self, BlockImport, BlockCheckParams, BlockImportParams, Error as ConsensusError, - ImportResult, - import_queue::CacheKeyId, -}; -use sp_core::traits::SpawnNamed; -use polkadot_primitives::v0::{ - Block, BlockId, Hash, - ParachainHost, ValidatorId, AbridgedCandidateReceipt, AvailableData, - ValidatorPair, ErasureChunk, -}; -use futures::{prelude::*, future::select, channel::{mpsc, oneshot}}; -use futures::future::AbortHandle; -use keystore::KeyStorePtr; - -use tokio::runtime::{Handle, Runtime as LocalRuntime}; - -use crate::{LOG_TARGET, ErasureNetworking}; -use crate::store::Store; - -/// Errors that may occur. -#[derive(Debug, derive_more::Display, derive_more::From)] -pub(crate) enum Error { - #[from] - StoreError(io::Error), - #[display(fmt = "Validator's id and number of validators at block with parent {} not found", relay_parent)] - IdAndNValidatorsNotFound { relay_parent: Hash }, -} - -/// Used in testing to interact with the worker thread. -#[cfg(test)] -pub(crate) struct WithWorker(Box); - -#[cfg(test)] -impl std::fmt::Debug for WithWorker { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "") - } -} - -/// Messages sent to the `Worker`. -/// -/// Messages are sent in a number of different scenarios, -/// for instance, when: -/// * importing blocks in `BlockImport` implementation, -/// * recieving finality notifications, -/// * when the `Store` api is used by outside code. -#[derive(Debug)] -pub(crate) enum WorkerMsg { - IncludedParachainBlocks(IncludedParachainBlocks), - Chunks(Chunks), - CandidatesFinalized(CandidatesFinalized), - MakeAvailable(MakeAvailable), - #[cfg(test)] - WithWorker(WithWorker), -} - -/// A notification of a parachain block included in the relay chain. -#[derive(Debug)] -pub(crate) struct IncludedParachainBlock { - /// The abridged candidate receipt, extracted from a relay-chain block. - pub candidate: AbridgedCandidateReceipt, - /// The data to keep available from the candidate, if known. - pub available_data: Option, -} - -/// The receipts of the heads included into the block with a given parent. -#[derive(Debug)] -pub(crate) struct IncludedParachainBlocks { - /// The blocks themselves. - pub blocks: Vec, - /// A sender to signal the result asynchronously. - pub result: oneshot::Sender>, -} - -/// We have received chunks we requested. -#[derive(Debug)] -pub(crate) struct Chunks { - /// The hash of the parachain candidate these chunks belong to. - pub candidate_hash: Hash, - /// The chunks - pub chunks: Vec, - /// The number of validators present at the candidate's relay-parent. - pub n_validators: u32, - /// A sender to signal the result asynchronously. - pub result: oneshot::Sender>, -} - -/// These candidates have been finalized, so unneded availability may be now pruned -#[derive(Debug)] -pub(crate) struct CandidatesFinalized { - /// The relay parent of the block that was finalized. - relay_parent: Hash, - /// The hashes of candidates that were finalized in this block. - included_candidates: HashSet, -} - -/// The message that corresponds to `make_available` call of the crate API. -#[derive(Debug)] -pub(crate) struct MakeAvailable { - /// The hash of the candidate for which we are publishing data. - pub candidate_hash: Hash, - /// The data to make available. - pub available_data: AvailableData, - /// A sender to signal the result asynchronously. - pub result: oneshot::Sender>, -} - -/// Description of a chunk we are listening for. -#[derive(Hash, Debug, PartialEq, Eq)] -struct ListeningKey { - candidate_hash: Hash, - index: u32, -} - -/// An availability worker with it's inner state. -pub(super) struct Worker { - availability_store: Store, - listening_for: HashMap, - - sender: mpsc::UnboundedSender, -} - -/// The handle to the `Worker`. -pub(super) struct WorkerHandle { - thread: Option>>, - sender: mpsc::UnboundedSender, - exit_signal: Option, -} - -impl WorkerHandle { - pub(crate) fn to_worker(&self) -> &mpsc::UnboundedSender { - &self.sender - } -} - -impl Drop for WorkerHandle { - fn drop(&mut self) { - if let Some(signal) = self.exit_signal.take() { - let _ = signal.fire(); - } - - if let Some(thread) = self.thread.take() { - if let Err(_) = thread.join() { - error!(target: LOG_TARGET, "Errored stopping the thread"); - } - } - } -} - - -fn fetch_candidates

(client: &P, extrinsics: Vec<::Extrinsic>, parent: &BlockId) - -> ClientResult>> -where - P: ProvideRuntimeApi, - P::Api: ParachainHost, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, -{ - let api = client.runtime_api(); - - let candidates = if api.has_api_with::, _>( - parent, - |version| version >= 2, - ).map_err(|e| ConsensusError::ChainLookup(e.to_string()))? { - api.get_heads(&parent, extrinsics) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? - } else { - None - }; - - Ok(candidates) -} - -/// Creates a task to prune entries in availability store upon block finalization. -async fn prune_unneeded_availability(client: Arc

, mut sender: S) -where - P: ProvideRuntimeApi + BlockchainEvents + BlockBackend + Send + Sync + 'static, - P::Api: ParachainHost + ApiExt, - S: Sink + Clone + Send + Sync + Unpin, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, -{ - let mut finality_notification_stream = client.finality_notification_stream(); - - while let Some(notification) = finality_notification_stream.next().await { - let hash = notification.hash; - let parent_hash = notification.header.parent_hash; - let extrinsics = match client.block_body(&BlockId::hash(hash)) { - Ok(Some(extrinsics)) => extrinsics, - Ok(None) => { - error!( - target: LOG_TARGET, - "No block body found for imported block {:?}", - hash, - ); - continue; - } - Err(e) => { - error!( - target: LOG_TARGET, - "Failed to get block body for imported block {:?}: {:?}", - hash, - e, - ); - continue; - } - }; - - let included_candidates = match fetch_candidates( - &*client, - extrinsics, - &BlockId::hash(parent_hash), - ) { - Ok(Some(candidates)) => candidates - .into_iter() - .map(|c| c.hash()) - .collect(), - Ok(None) => { - warn!( - target: LOG_TARGET, - "Failed to extract candidates from block body of imported block {:?}", hash - ); - continue; - } - Err(e) => { - warn!( - target: LOG_TARGET, - "Failed to fetch block body for imported block {:?}: {:?}", hash, e - ); - continue; - } - }; - - let msg = WorkerMsg::CandidatesFinalized(CandidatesFinalized { - relay_parent: parent_hash, - included_candidates - }); - - if let Err(_) = sender.send(msg).await { - break; - } - } -} - -impl Worker { - - // Called on startup of the worker to initiate fetch from network for all awaited chunks. - fn initiate_all_fetches( - &mut self, - runtime_handle: &Handle, - erasure_network: &EN, - sender: &mut mpsc::UnboundedSender, - ) { - if let Some(awaited_chunks) = self.availability_store.awaited_chunks() { - for awaited_chunk in awaited_chunks { - if let Err(e) = self.initiate_fetch( - runtime_handle, - erasure_network, - sender, - awaited_chunk.relay_parent, - awaited_chunk.candidate_hash, - ) { - warn!(target: LOG_TARGET, "Failed to register network listener: {}", e); - } - } - } - } - - // initiates a fetch from network for the described chunk, with our local index. - fn initiate_fetch( - &mut self, - runtime_handle: &Handle, - erasure_network: &EN, - sender: &mut mpsc::UnboundedSender, - relay_parent: Hash, - candidate_hash: Hash, - ) -> Result<(), Error> { - let (local_id, n_validators) = self.availability_store - .get_validator_index_and_n_validators(&relay_parent) - .ok_or(Error::IdAndNValidatorsNotFound { relay_parent })?; - - // fast exit for if we already have the chunk. - if self.availability_store.get_erasure_chunk(&candidate_hash, local_id as _).is_some() { - return Ok(()) - } - - trace!( - target: LOG_TARGET, - "Initiating fetch for erasure-chunk at parent {} with candidate-hash {}", - relay_parent, - candidate_hash, - ); - - let fut = erasure_network.fetch_erasure_chunk(&candidate_hash, local_id); - let mut sender = sender.clone(); - let (fut, signal) = future::abortable(async move { - let chunk = match fut.await { - Ok(chunk) => chunk, - Err(e) => { - warn!(target: LOG_TARGET, "Unable to fetch erasure-chunk from network: {:?}", e); - return - } - }; - let (s, _) = oneshot::channel(); - let _ = sender.send(WorkerMsg::Chunks(Chunks { - candidate_hash, - chunks: vec![chunk], - n_validators, - result: s, - })).await; - }.map(drop).boxed()); - - - let key = ListeningKey { - candidate_hash, - index: local_id, - }; - - self.listening_for.insert(key, signal); - let _ = runtime_handle.spawn(fut); - - Ok(()) - } - - fn on_parachain_blocks_received( - &mut self, - runtime_handle: &Handle, - erasure_network: &EN, - sender: &mut mpsc::UnboundedSender, - blocks: Vec, - ) -> Result<(), Error> { - // First we have to add the receipts themselves. - for IncludedParachainBlock { candidate, available_data } - in blocks.into_iter() - { - let _ = self.availability_store.add_candidate(&candidate); - - if let Some(_available_data) = available_data { - // Should we be breaking block into chunks here and gossiping it and so on? - } - - // This leans on the codebase-wide assumption that the `relay_parent` - // of all candidates in a block matches the parent hash of that block. - // - // In the future this will not always be true. - let candidate_hash = candidate.hash(); - let _ = self.availability_store.note_candidates_with_relay_parent( - &candidate.relay_parent, - &[candidate_hash], - ); - - if let Err(e) = self.initiate_fetch( - runtime_handle, - erasure_network, - sender, - candidate.relay_parent, - candidate_hash, - ) { - warn!(target: LOG_TARGET, "Failed to register chunk listener: {}", e); - } - } - - Ok(()) - } - - // Handles chunks that were required. - fn on_chunks( - &mut self, - candidate_hash: Hash, - chunks: Vec, - n_validators: u32, - ) -> Result<(), Error> { - for c in &chunks { - let key = ListeningKey { - candidate_hash, - index: c.index, - }; - - // remove bookkeeping so network does not attempt to fetch - // any longer. - if let Some(exit_signal) = self.listening_for.remove(&key) { - exit_signal.abort(); - } - } - - self.availability_store.add_erasure_chunks( - n_validators, - &candidate_hash, - chunks, - )?; - - Ok(()) - } - - /// Starts a worker with a given availability store and a gossip messages provider. - pub fn start( - availability_store: Store, - erasure_network: EN, - ) -> WorkerHandle { - let (sender, mut receiver) = mpsc::unbounded(); - - let mut worker = Worker { - availability_store, - listening_for: HashMap::new(), - sender: sender.clone(), - }; - - let sender = sender.clone(); - let (signal, exit) = exit_future::signal(); - - let handle = thread::spawn(move || -> io::Result<()> { - let mut runtime = LocalRuntime::new()?; - let mut sender = worker.sender.clone(); - - let runtime_handle = runtime.handle().clone(); - - // On startup, initiates fetch from network for all - // entries in the awaited frontier. - worker.initiate_all_fetches(runtime.handle(), &erasure_network, &mut sender); - - let process_notification = async move { - while let Some(msg) = receiver.next().await { - trace!(target: LOG_TARGET, "Received message {:?}", msg); - - let res = match msg { - WorkerMsg::IncludedParachainBlocks(msg) => { - let IncludedParachainBlocks { - blocks, - result, - } = msg; - - let res = worker.on_parachain_blocks_received( - &runtime_handle, - &erasure_network, - &mut sender, - blocks, - ); - - let _ = result.send(res); - Ok(()) - } - WorkerMsg::Chunks(msg) => { - let Chunks { - candidate_hash, - chunks, - n_validators, - result, - } = msg; - - let res = worker.on_chunks( - candidate_hash, - chunks, - n_validators, - ); - - let _ = result.send(res); - Ok(()) - } - WorkerMsg::CandidatesFinalized(msg) => { - let CandidatesFinalized { relay_parent, included_candidates } = msg; - - worker.availability_store.candidates_finalized( - relay_parent, - included_candidates, - ) - } - WorkerMsg::MakeAvailable(msg) => { - let MakeAvailable { candidate_hash, available_data, result } = msg; - let res = worker.availability_store - .make_available(candidate_hash, available_data) - .map_err(|e| e.into()); - let _ = result.send(res); - Ok(()) - } - #[cfg(test)] - WorkerMsg::WithWorker(with_worker) => { - (with_worker.0)(&mut worker); - Ok(()) - } - }; - - if let Err(_) = res { - warn!(target: LOG_TARGET, "An error occured while processing a message"); - } - } - - }; - - runtime.spawn(select(process_notification.boxed(), exit.clone()).map(drop)); - runtime.block_on(exit); - - info!(target: LOG_TARGET, "Availability worker exiting"); - - Ok(()) - }); - - WorkerHandle { - thread: Some(handle), - sender, - exit_signal: Some(signal), - } - } -} - -/// Implementer of the [`BlockImport`] trait. -/// -/// Used to embed `availability-store` logic into the block imporing pipeline. -/// -/// [`BlockImport`]: https://substrate.dev/rustdocs/v1.0/substrate_consensus_common/trait.BlockImport.html -pub struct AvailabilityBlockImport { - inner: I, - client: Arc

, - keystore: KeyStorePtr, - to_worker: mpsc::UnboundedSender, - exit_signal: AbortHandle, -} - -impl Drop for AvailabilityBlockImport { - fn drop(&mut self) { - self.exit_signal.abort(); - } -} - -impl BlockImport for AvailabilityBlockImport where - I: BlockImport> + Send + Sync, - I::Error: Into, - P: ProvideRuntimeApi + ProvideCache, - P::Api: ParachainHost, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend -{ - type Error = ConsensusError; - type Transaction = sp_api::TransactionFor; - - fn import_block( - &mut self, - block: BlockImportParams, - new_cache: HashMap>, - ) -> Result { - trace!( - target: LOG_TARGET, - "Importing block #{}, ({})", - block.header.number(), - block.post_hash(), - ); - - if let Some(ref extrinsics) = block.body { - let parent_id = BlockId::hash(*block.header.parent_hash()); - // Extract our local position i from the validator set of the parent. - let validators = self.client.runtime_api().validators(&parent_id) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()))?; - - let our_id = self.our_id(&validators); - - // Use a runtime API to extract all included erasure-roots from the imported block. - let candidates = fetch_candidates(&*self.client, extrinsics.clone(), &parent_id) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()))?; - - match candidates { - Some(candidates) => { - match our_id { - Some(our_id) => { - trace!( - target: LOG_TARGET, - "Our validator id is {}, the candidates included are {:?}", - our_id, - candidates, - ); - - let (s, _) = oneshot::channel(); - - // Inform the worker about the included parachain blocks. - let blocks = candidates - .into_iter() - .map(|c| IncludedParachainBlock { - candidate: c, - available_data: None, - }) - .collect(); - - let msg = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { - blocks, - result: s, - }); - - let _ = self.to_worker.unbounded_send(msg); - } - None => (), - } - } - None => { - trace!( - target: LOG_TARGET, - "No parachain heads were included in block {}", block.header.hash() - ); - }, - } - } - - self.inner.import_block(block, new_cache).map_err(Into::into) - } - - fn check_block( - &mut self, - block: BlockCheckParams, - ) -> Result { - self.inner.check_block(block).map_err(Into::into) - } -} - -impl AvailabilityBlockImport { - pub(crate) fn new( - client: Arc

, - block_import: I, - spawner: impl SpawnNamed, - keystore: KeyStorePtr, - to_worker: mpsc::UnboundedSender, - ) -> Self - where - P: ProvideRuntimeApi + BlockBackend + BlockchainEvents + Send + Sync + 'static, - P::Api: ParachainHost, - P::Api: ApiExt, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, - { - // This is not the right place to spawn the finality future, - // it would be more appropriate to spawn it in the `start` method of the `Worker`. - // However, this would make the type of the `Worker` and the `Store` itself - // dependent on the types of client and executor, which would prove - // not not so handy in the testing code. - let (prune_available, exit_signal) = future::abortable(prune_unneeded_availability( - client.clone(), - to_worker.clone(), - )); - - spawner.spawn("polkadot-prune-availibility", prune_available.map(drop).boxed()); - - AvailabilityBlockImport { - client, - inner: block_import, - to_worker, - keystore, - exit_signal, - } - } - - fn our_id(&self, validators: &[ValidatorId]) -> Option { - let keystore = self.keystore.read(); - validators - .iter() - .enumerate() - .find_map(|(i, v)| { - keystore.key_pair::(&v).map(|_| i as u32).ok() - }) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - use futures::channel::oneshot; - use std::sync::Arc; - use std::pin::Pin; - use tokio::runtime::Runtime; - use parking_lot::Mutex; - use crate::store::AwaitedFrontierEntry; - - #[derive(Default, Clone)] - struct TestErasureNetwork { - chunk_receivers: Arc - >>>, - } - - impl TestErasureNetwork { - // adds a receiver. this returns a sender for the erasure-chunk - // along with an exit future that fires when the erasure chunk has - // been fully-processed - fn add_receiver(&self, candidate_hash: Hash, index: u32) - -> oneshot::Sender - { - let (sender, receiver) = oneshot::channel(); - self.chunk_receivers.lock().insert((candidate_hash, index), receiver); - sender - } - } - - impl ErasureNetworking for TestErasureNetwork { - type Error = String; - - fn fetch_erasure_chunk(&self, candidate_hash: &Hash, index: u32) - -> Pin> + Send>> - { - match self.chunk_receivers.lock().remove(&(*candidate_hash, index)) { - Some(receiver) => receiver.then(|x| match x { - Ok(x) => future::ready(Ok(x)).left_future(), - Err(_) => future::pending().right_future(), - }).boxed(), - None => future::pending().boxed(), - } - } - - fn distribute_erasure_chunk( - &self, - _candidate_hash: Hash, - _chunk: ErasureChunk - ) {} - } - - // This test tests that as soon as the worker receives info about new parachain blocks - // included it registers gossip listeners for it's own chunks. Upon receiving the awaited - // chunk messages the corresponding listeners are deregistered and these chunks are removed - // from the awaited chunks set. - #[test] - fn receiving_gossip_chunk_removes_from_frontier() { - let mut runtime = Runtime::new().unwrap(); - let relay_parent = [1; 32].into(); - let local_id = 2; - let n_validators = 4; - - let store = Store::new_in_memory(); - - let mut candidate = AbridgedCandidateReceipt::default(); - - candidate.relay_parent = relay_parent; - let candidate_hash = candidate.hash(); - - // Tell the store our validator's position and the number of validators at given point. - store.note_validator_index_and_n_validators(&relay_parent, local_id, n_validators).unwrap(); - - let network = TestErasureNetwork::default(); - let chunk_sender = network.add_receiver(candidate_hash, local_id); - - // At this point we shouldn't be waiting for any chunks. - assert!(store.awaited_chunks().is_none()); - - let (s, r) = oneshot::channel(); - - let msg = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { - blocks: vec![IncludedParachainBlock { - candidate, - available_data: None, - }], - result: s, - }); - - let handle = Worker::start(store.clone(), network); - - // Tell the worker that the new blocks have been included into the relay chain. - // This should trigger the registration of gossip message listeners for the - // chunk topics. - handle.sender.unbounded_send(msg).unwrap(); - - runtime.block_on(r).unwrap().unwrap(); - - // Make sure that at this point we are waiting for the appropriate chunk. - assert_eq!( - store.awaited_chunks().unwrap(), - vec![AwaitedFrontierEntry { - relay_parent, - candidate_hash, - validator_index: local_id, - }].into_iter().collect() - ); - - // Complete the chunk request. - chunk_sender.send(ErasureChunk { - chunk: vec![1, 2, 3], - index: local_id as u32, - proof: vec![], - }).unwrap(); - - // wait until worker thread has de-registered the listener for a - // particular chunk. - loop { - let (s, r) = oneshot::channel(); - handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { - let key = ListeningKey { - candidate_hash, - index: local_id, - }; - - let is_waiting = worker.listening_for.contains_key(&key); - - s.send(!is_waiting).unwrap(); // tell the test thread `true` if we are not waiting. - })))).unwrap(); - - if runtime.block_on(r).unwrap() { - break - } - } - - // The awaited chunk has been received so at this point we no longer wait for any chunks. - assert_eq!(store.awaited_chunks().unwrap().len(), 0); - } - - #[test] - fn included_parachain_blocks_registers_listener() { - let mut runtime = Runtime::new().unwrap(); - let relay_parent = [1; 32].into(); - let erasure_root_1 = [2; 32].into(); - let erasure_root_2 = [3; 32].into(); - let pov_block_hash_1 = [4; 32].into(); - let pov_block_hash_2 = [5; 32].into(); - let local_id = 2; - let n_validators = 4; - - let mut candidate_1 = AbridgedCandidateReceipt::default(); - candidate_1.commitments.erasure_root = erasure_root_1; - candidate_1.pov_block_hash = pov_block_hash_1; - candidate_1.relay_parent = relay_parent; - let candidate_1_hash = candidate_1.hash(); - - let mut candidate_2 = AbridgedCandidateReceipt::default(); - candidate_2.commitments.erasure_root = erasure_root_2; - candidate_2.pov_block_hash = pov_block_hash_2; - candidate_2.relay_parent = relay_parent; - let candidate_2_hash = candidate_2.hash(); - - let store = Store::new_in_memory(); - - // Tell the store our validator's position and the number of validators at given point. - store.note_validator_index_and_n_validators(&relay_parent, local_id, n_validators).unwrap(); - - // Let the store know about the candidates - store.add_candidate(&candidate_1).unwrap(); - store.add_candidate(&candidate_2).unwrap(); - - // And let the store know about the chunk from the second candidate. - store.add_erasure_chunks( - n_validators, - &candidate_2_hash, - vec![ErasureChunk { - chunk: vec![1, 2, 3], - index: local_id, - proof: Vec::default(), - }], - ).unwrap(); - - let network = TestErasureNetwork::default(); - let _ = network.add_receiver(candidate_1_hash, local_id); - let _ = network.add_receiver(candidate_2_hash, local_id); - - let handle = Worker::start(store.clone(), network.clone()); - - { - let (s, r) = oneshot::channel(); - // Tell the worker to listen for chunks from candidate 2 (we alredy have a chunk from it). - let listen_msg_2 = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { - blocks: vec![IncludedParachainBlock { - candidate: candidate_2, - available_data: None, - }], - result: s, - }); - - handle.sender.unbounded_send(listen_msg_2).unwrap(); - - runtime.block_on(r).unwrap().unwrap(); - // The receiver for this chunk left intact => listener not registered. - assert!(network.chunk_receivers.lock().contains_key(&(candidate_2_hash, local_id))); - - // more directly: - let (s, r) = oneshot::channel(); - handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { - let key = ListeningKey { - candidate_hash: candidate_2_hash, - index: local_id, - }; - let _ = s.send(worker.listening_for.contains_key(&key)); - })))).unwrap(); - - assert!(!runtime.block_on(r).unwrap()); - } - - { - let (s, r) = oneshot::channel(); - - // Tell the worker to listen for chunks from candidate 1. - // (we don't have a chunk from it yet). - let listen_msg_1 = WorkerMsg::IncludedParachainBlocks(IncludedParachainBlocks { - blocks: vec![IncludedParachainBlock { - candidate: candidate_1, - available_data: None, - }], - result: s, - }); - - handle.sender.unbounded_send(listen_msg_1).unwrap(); - runtime.block_on(r).unwrap().unwrap(); - - // The receiver taken => listener registered. - assert!(!network.chunk_receivers.lock().contains_key(&(candidate_1_hash, local_id))); - - - // more directly: - let (s, r) = oneshot::channel(); - handle.sender.unbounded_send(WorkerMsg::WithWorker(WithWorker(Box::new(move |worker| { - let key = ListeningKey { - candidate_hash: candidate_1_hash, - index: local_id, - }; - let _ = s.send(worker.listening_for.contains_key(&key)); - })))).unwrap(); - - assert!(runtime.block_on(r).unwrap()); - } - } -} diff --git a/cli/src/command.rs b/cli/src/command.rs index 4952bdf9c137..964e13e6d3fd 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -144,9 +144,7 @@ pub fn run() -> Result<()> { _ => service::build_full( config, None, - None, authority_discovery_enabled, - 6000, grandpa_pause, ).map(|r| r.0), } diff --git a/collator/Cargo.toml b/collator/Cargo.toml deleted file mode 100644 index 38cb3ee2e23c..000000000000 --- a/collator/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "polkadot-collator" -version = "0.8.22" -authors = ["Parity Technologies "] -description = "Collator node implementation" -edition = "2018" - -[dependencies] -futures = "0.3.4" -sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-cli = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } -consensus_common = { package = "sp-consensus", git = "https://github.com/paritytech/substrate", branch = "master" } -polkadot-primitives = { path = "../primitives" } -polkadot-cli = { path = "../cli" } -polkadot-network = { path = "../network" } -polkadot-validation = { path = "../validation" } -polkadot-service = { path = "../service", optional = true} -polkadot-service-new = { path = "../node/service", optional = true } -log = "0.4.8" -tokio = "0.2.13" -futures-timer = "2.0" -codec = { package = "parity-scale-codec", version = "1.3.4" } - -[dev-dependencies] -keyring = { package = "sp-keyring", git = "https://github.com/paritytech/substrate", branch = "master" } - -[features] -default = ["service-old"] -service-old = [ "polkadot-service" ] -service-rewr = [ "polkadot-service-new" ] diff --git a/collator/README.adoc b/collator/README.adoc deleted file mode 100644 index d302cd2af0fe..000000000000 --- a/collator/README.adoc +++ /dev/null @@ -1,5 +0,0 @@ - -= Polkadot Collator - -placeholder -//TODO Write content :) (https://github.com/paritytech/polkadot/issues/159) diff --git a/collator/src/lib.rs b/collator/src/lib.rs deleted file mode 100644 index 8e0cabb0eb99..000000000000 --- a/collator/src/lib.rs +++ /dev/null @@ -1,496 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Collation node logic. -//! -//! A collator node lives on a distinct parachain and submits a proposal for -//! a state transition, along with a proof for its validity -//! (what we might call a witness or block data). -//! -//! One of collators' other roles is to route messages between chains. -//! Each parachain produces a list of "egress" posts of messages for each other -//! parachain on each block, for a total of N^2 lists all together. -//! -//! We will refer to the egress list at relay chain block X of parachain A with -//! destination B as egress(X)[A -> B] -//! -//! On every block, each parachain will be intended to route messages from some -//! subset of all the other parachains. (NOTE: in practice this is not done until PoC-3) -//! -//! Since the egress information is unique to every block, when routing from a -//! parachain a collator must gather all egress posts from that parachain -//! up to the last point in history that messages were successfully routed -//! from that parachain, accounting for relay chain blocks where no candidate -//! from the collator's parachain was produced. -//! -//! In the case that all parachains route to each other and a candidate for the -//! collator's parachain was included in the last relay chain block, the collator -//! only has to gather egress posts from other parachains one block back in relay -//! chain history. -//! -//! This crate defines traits which provide context necessary for collation logic -//! to be performed, as the collation logic itself. - -use std::collections::HashSet; -use std::fmt; -use std::sync::Arc; -use std::time::Duration; -use std::pin::Pin; - -use futures::{future, Future, Stream, FutureExt, StreamExt}; -use sp_core::Pair; -use polkadot_primitives::v0::{ - BlockId, Hash, Block, DownwardMessage, - BlockData, DutyRoster, HeadData, Id as ParaId, - PoVBlock, ValidatorId, CollatorPair, LocalValidationData, GlobalValidationData, - Collation, CollationInfo, collator_signature_payload, -}; -use polkadot_cli::service::{self, Role}; -pub use polkadot_cli::service::Configuration; -pub use polkadot_cli::Cli; -pub use polkadot_validation::SignedStatement; -pub use polkadot_primitives::v0::CollatorId; -pub use sc_network::PeerId; -pub use service::{RuntimeApiCollection, Client}; -pub use sc_cli::SubstrateCli; -#[cfg(not(feature = "service-rewr"))] -use polkadot_service::{FullNodeHandles, AbstractClient, ClientHandle}; -#[cfg(feature = "service-rewr")] -use polkadot_service_new::{ - self as polkadot_service, - Error as ServiceError, FullNodeHandles, AbstractClient, -}; -use sc_service::SpawnTaskHandle; -use sp_core::traits::SpawnNamed; -use sp_runtime::traits::BlakeTwo256; -use consensus_common::SyncOracle; -use sc_client_api::Backend as BackendT; - -const COLLATION_TIMEOUT: Duration = Duration::from_secs(30); - -/// An abstraction over the `Network` with useful functions for a `Collator`. -pub trait Network: Send + Sync { - /// Create a `Stream` of checked statements for the given `relay_parent`. - /// - /// The returned stream will not terminate, so it is required to make sure that the stream is - /// dropped when it is not required anymore. Otherwise, it will stick around in memory - /// infinitely. - fn checked_statements(&self, relay_parent: Hash) -> Pin + Send>>; -} - -impl Network for polkadot_network::protocol::Service { - fn checked_statements(&self, relay_parent: Hash) -> Pin + Send>> { - polkadot_network::protocol::Service::checked_statements(self, relay_parent).boxed() - } -} - -/// Collation errors. -#[derive(Debug)] -pub enum Error { - /// Error on the relay-chain side of things. - Polkadot(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Error::Polkadot(ref err) => write!(f, "Polkadot node error: {}", err), - } - } -} - -/// Something that can build a `ParachainContext`. -pub trait BuildParachainContext { - /// The parachain context produced by the `build` function. - type ParachainContext: self::ParachainContext; - - /// Build the `ParachainContext`. - fn build( - self, - client: Arc, - spawner: SP, - network: impl Network + SyncOracle + Clone + 'static, - ) -> Result - where - SP: SpawnNamed + Clone + Send + Sync + 'static, - Backend: BackendT, - Backend::State: sp_api::StateBackend, - Client: polkadot_service::AbstractClient + 'static, - Client::Api: RuntimeApiCollection, - ; -} - -/// Parachain context needed for collation. -/// -/// This can be implemented through an externally attached service or a stub. -/// This is expected to be a lightweight, shared type like an Arc. -pub trait ParachainContext: Clone { - type ProduceCandidate: Future>; - - /// Produce a candidate, given the relay parent hash, the latest ingress queue information - /// and the last parachain head. - fn produce_candidate( - &mut self, - relay_parent: Hash, - global_validation: GlobalValidationData, - local_validation: LocalValidationData, - downward_messages: Vec, - ) -> Self::ProduceCandidate; -} - -/// Produce a candidate for the parachain, with given contexts, parent head, and signing key. -pub async fn collate

( - relay_parent: Hash, - local_id: ParaId, - global_validation: GlobalValidationData, - local_validation_data: LocalValidationData, - downward_messages: Vec, - mut para_context: P, - key: Arc, -) -> Option - where - P: ParachainContext, - P::ProduceCandidate: Send, -{ - let (block_data, head_data) = para_context.produce_candidate( - relay_parent, - global_validation, - local_validation_data, - downward_messages, - ).await?; - - let pov_block = PoVBlock { - block_data, - }; - - let pov_block_hash = pov_block.hash(); - let signature = key.sign(&collator_signature_payload( - &relay_parent, - &local_id, - &pov_block_hash, - )); - - let info = CollationInfo { - parachain_index: local_id, - relay_parent, - collator: key.public(), - signature, - head_data, - pov_block_hash, - }; - - let collation = Collation { - info, - pov: pov_block, - }; - - Some(collation) -} - -/// Build a collator service based on the `ClientHandle`. -#[cfg(feature = "service-rewr")] -pub fn build_collator_service

( - spawner: SpawnTaskHandle, - handles: FullNodeHandles, - client: impl ClientHandle, - para_id: ParaId, - key: Arc, - build_parachain_context: P, -) -> Result + Send + 'static>>, polkadot_service::Error> - where - P: BuildParachainContext, - P::ParachainContext: Send + 'static, - ::ProduceCandidate: Send, -{ - Err("Collator is not functional with the new service yet".into()) -} - -struct BuildCollationWork

{ - handles: polkadot_service::FullNodeHandles, - para_id: ParaId, - key: Arc, - build_parachain_context: P, - spawner: SpawnTaskHandle, -} - -impl

polkadot_service::ExecuteWithClient for BuildCollationWork

- where - P: BuildParachainContext, - P::ParachainContext: Send + 'static, - ::ProduceCandidate: Send, -{ - type Output = Result + Send + 'static>>, polkadot_service::Error>; - - fn execute_with_client(self, client: Arc) -> Self::Output - where>::StateBackend: sp_api::StateBackend, - Backend: sc_client_api::Backend, - Backend::State: sp_api::StateBackend, - Api: RuntimeApiCollection, - Client: AbstractClient + 'static, - { - let polkadot_network = self.handles - .polkadot_network - .ok_or_else(|| "Collator cannot run when Polkadot-specific networking has not been started")?; - - // We don't require this here, but we need to make sure that the validation service is started. - // This service makes sure the collator is joining the correct gossip topics and receives the appropiate - // messages. - self.handles.validation_service_handle - .ok_or_else(|| "Collator cannot run when validation networking has not been started")?; - - let parachain_context = match self.build_parachain_context.build( - client.clone(), - self.spawner.clone(), - polkadot_network.clone(), - ) { - Ok(ctx) => ctx, - Err(()) => { - return Err("Could not build the parachain context!".into()) - } - }; - - let key = self.key; - let para_id = self.para_id; - let spawner = self.spawner; - - let res = async move { - let mut notification_stream = client.import_notification_stream(); - - while let Some(notification) = notification_stream.next().await { - macro_rules! try_fr { - ($e:expr) => { - match $e { - Ok(x) => x, - Err(e) => return future::Either::Left(future::err(Error::Polkadot( - format!("{:?}", e) - ))), - } - } - } - - let relay_parent = notification.hash; - let id = BlockId::hash(relay_parent); - - let network = polkadot_network.clone(); - let client = client.clone(); - let key = key.clone(); - let parachain_context = parachain_context.clone(); - - let work = future::lazy(move |_| { - let api = client.runtime_api(); - let global_validation = try_fr!(api.global_validation_data(&id)); - let local_validation = match try_fr!(api.local_validation_data(&id, para_id)) { - Some(local_validation) => local_validation, - None => return future::Either::Left(future::ok(())), - }; - let downward_messages = try_fr!(api.downward_messages(&id, para_id)); - - let validators = try_fr!(api.validators(&id)); - - let targets = compute_targets( - para_id, - validators.as_slice(), - try_fr!(api.duty_roster(&id)), - ); - - let collation_work = collate( - relay_parent, - para_id, - global_validation, - local_validation, - downward_messages, - parachain_context, - key, - ).map(move |collation| { - match collation { - Some(collation) => network.distribute_collation(targets, collation), - None => log::trace!("Skipping collation as `collate` returned `None`"), - } - - Ok(()) - }); - - future::Either::Right(collation_work) - }); - - let deadlined = future::select( - work.then(|f| f).boxed(), - futures_timer::Delay::new(COLLATION_TIMEOUT) - ); - - let silenced = deadlined - .map(|either| { - match either { - future::Either::Right(_) => log::warn!("Collation failure: timeout"), - future::Either::Left((Err(e), _)) => { - log::error!("Collation failed: {:?}", e) - } - future::Either::Left((Ok(()), _)) => {}, - } - }); - - let future = silenced.map(drop); - - spawner.spawn("collation-work", future); - } - }; - - Ok(res.boxed()) - } -} - -/// Build a collator service based on the `ClientHandle`. -#[cfg(not(feature = "service-rewr"))] -pub fn build_collator_service

( - spawner: SpawnTaskHandle, - handles: FullNodeHandles, - client: impl ClientHandle, - para_id: ParaId, - key: Arc, - build_parachain_context: P, -) -> Result + Send + 'static>>, polkadot_service::Error> - where - P: BuildParachainContext, - P::ParachainContext: Send + 'static, - ::ProduceCandidate: Send, -{ - client.execute_with(BuildCollationWork { - handles, - para_id, - key, - build_parachain_context, - spawner, - }) -} - -/// Async function that will run the collator node with the given `RelayChainContext` and `ParachainContext` -/// built by the given `BuildParachainContext` and arguments to the underlying polkadot node. -pub fn start_collator

( - build_parachain_context: P, - para_id: ParaId, - key: Arc, - config: Configuration, -) -> Result< - (Pin + Send>>, sc_service::TaskManager), - polkadot_service::Error -> -where - P: 'static + BuildParachainContext, - P::ParachainContext: Send + 'static, - ::ProduceCandidate: Send, -{ - if matches!(config.role, Role::Light) { - return Err( - polkadot_service::Error::Other("light nodes are unsupported as collator".into()) - .into()); - } - - let (task_manager, client, handles) = polkadot_service::build_full( - config, - Some((key.public(), para_id)), - None, - false, - 6000, - None, - )?; - - let future = build_collator_service( - task_manager.spawn_handle(), - handles, - client, - para_id, - key, - build_parachain_context, - )?; - - Ok((future, task_manager)) -} - -#[cfg(not(feature = "service-rewr"))] -fn compute_targets(para_id: ParaId, session_keys: &[ValidatorId], roster: DutyRoster) -> HashSet { - use polkadot_primitives::v0::Chain; - - roster.validator_duty.iter().enumerate() - .filter(|&(_, c)| c == &Chain::Parachain(para_id)) - .filter_map(|(i, _)| session_keys.get(i)) - .cloned() - .collect() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[derive(Clone)] - struct DummyParachainContext; - - impl ParachainContext for DummyParachainContext { - type ProduceCandidate = future::Ready>; - - fn produce_candidate( - &mut self, - _relay_parent: Hash, - _global: GlobalValidationData, - _local_validation: LocalValidationData, - _: Vec, - ) -> Self::ProduceCandidate { - // send messages right back. - future::ready(Some(( - BlockData(vec![1, 2, 3, 4, 5,]), - HeadData(vec![9, 9, 9]), - ))) - } - } - - struct BuildDummyParachainContext; - - impl BuildParachainContext for BuildDummyParachainContext { - type ParachainContext = DummyParachainContext; - - fn build( - self, - _: Arc, - _: SP, - _: impl Network + Clone + 'static, - ) -> Result - where - SP: SpawnNamed + Clone + Send + Sync + 'static, - Backend: BackendT, - Backend::State: sp_api::StateBackend, - Client: polkadot_service::AbstractClient + 'static, - Client::Api: RuntimeApiCollection, - { - Ok(DummyParachainContext) - } - } - - // Make sure that the future returned by `start_collator` implements `Send`. - #[test] - fn start_collator_is_send() { - fn check_send(_: T) {} - - let cli = Cli::from_iter(&["-dev"]); - let task_executor = |_, _| async {}; - let config = cli.create_configuration(&cli.run.base, task_executor.into()).unwrap(); - - check_send(start_collator( - BuildDummyParachainContext, - 0.into(), - Arc::new(CollatorPair::generate().0), - config, - )); - } -} diff --git a/network/Cargo.toml b/network/Cargo.toml deleted file mode 100644 index 748bc20c097d..000000000000 --- a/network/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "polkadot-network" -version = "0.8.22" -authors = ["Parity Technologies "] -description = "Polkadot-specific networking protocol" -edition = "2018" - -[dependencies] -arrayvec = "0.4.12" -bytes = "0.5" -parking_lot = "0.9.0" -derive_more = "0.14.1" -av_store = { package = "polkadot-availability-store", path = "../availability-store" } -polkadot-validation = { path = "../validation" } -polkadot-primitives = { path = "../primitives" } -polkadot-erasure-coding = { path = "../erasure-coding" } -codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } -sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-network-gossip = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } -futures = "0.3.5" -log = "0.4.8" -exit-future = "0.2.0" -futures-timer = "2.0" -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -wasm-timer = "0.2.4" -rand = "0.7.3" - -[dev-dependencies] -sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-state-machine = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/network/README.adoc b/network/README.adoc deleted file mode 100644 index 25b3e003d5b0..000000000000 --- a/network/README.adoc +++ /dev/null @@ -1,5 +0,0 @@ - -= Polkadot Network - -placeholder -//TODO Write content :) (https://github.com/paritytech/polkadot/issues/159) diff --git a/network/src/legacy/collator_pool.rs b/network/src/legacy/collator_pool.rs deleted file mode 100644 index f2b168e0f592..000000000000 --- a/network/src/legacy/collator_pool.rs +++ /dev/null @@ -1,331 +0,0 @@ -// Copyright 2018-2020 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 . - -//! Bridge between the network and consensus service for getting collations to it. - -use codec::{Encode, Decode}; -use polkadot_primitives::v0::{Hash, CollatorId, Id as ParaId, Collation}; -use sc_network::PeerId; -use futures::channel::oneshot; - -use std::collections::hash_map::{HashMap, Entry}; -use std::time::Duration; -use wasm_timer::Instant; - -const COLLATION_LIFETIME: Duration = Duration::from_secs(60 * 5); - -/// The role of the collator. Whether they're the primary or backup for this parachain. -#[derive(PartialEq, Debug, Clone, Copy, Encode, Decode)] -pub enum Role { - /// Primary collators should send collations whenever it's time. - Primary = 0, - /// Backup collators should not. - Backup = 1, -} - -/// A maintenance action for the collator set. -#[derive(PartialEq, Debug)] -#[allow(dead_code)] -pub enum Action { - /// Disconnect the given collator. - Disconnect(CollatorId), - /// Give the collator a new role. - NewRole(CollatorId, Role), -} - -struct CollationSlot { - live_at: Instant, - entries: SlotEntries, -} - -impl CollationSlot { - fn blank_now() -> Self { - CollationSlot { - live_at: Instant::now(), - entries: SlotEntries::Blank, - } - } - - fn stay_alive(&self, now: Instant) -> bool { - self.live_at + COLLATION_LIFETIME > now - } -} - -#[derive(Debug)] -enum SlotEntries { - Blank, - // not queried yet - Pending(Vec), - // waiting for next to arrive. - Awaiting(Vec>), -} - -impl SlotEntries { - fn received_collation(&mut self, collation: Collation) { - *self = match std::mem::replace(self, SlotEntries::Blank) { - SlotEntries::Blank => SlotEntries::Pending(vec![collation]), - SlotEntries::Pending(mut cs) => { - cs.push(collation); - SlotEntries::Pending(cs) - } - SlotEntries::Awaiting(senders) => { - for sender in senders { - let _ = sender.send(collation.clone()); - } - - SlotEntries::Blank - } - }; - } - - fn await_with(&mut self, sender: oneshot::Sender) { - *self = match ::std::mem::replace(self, SlotEntries::Blank) { - SlotEntries::Blank => SlotEntries::Awaiting(vec![sender]), - SlotEntries::Awaiting(mut senders) => { - senders.push(sender); - SlotEntries::Awaiting(senders) - } - SlotEntries::Pending(mut cs) => { - let next_collation = cs.pop().expect("empty variant is always `Blank`; qed"); - let _ = sender.send(next_collation); - - if cs.is_empty() { - SlotEntries::Blank - } else { - SlotEntries::Pending(cs) - } - } - }; - } -} - -struct ParachainCollators { - primary: CollatorId, - backup: Vec, -} - -/// Manages connected collators and role assignments from the perspective of a validator. -#[derive(Default)] -pub struct CollatorPool { - collators: HashMap, - parachain_collators: HashMap, - collations: HashMap<(Hash, ParaId), CollationSlot>, -} - -impl CollatorPool { - /// Create a new `CollatorPool` object. - pub fn new() -> Self { - CollatorPool { - collators: HashMap::new(), - parachain_collators: HashMap::new(), - collations: HashMap::new(), - } - } - - /// Call when a new collator is authenticated. Returns the role. - pub fn on_new_collator(&mut self, collator_id: CollatorId, para_id: ParaId, peer_id: PeerId) -> Role { - self.collators.insert(collator_id.clone(), (para_id, peer_id)); - match self.parachain_collators.entry(para_id) { - Entry::Vacant(vacant) => { - vacant.insert(ParachainCollators { - primary: collator_id, - backup: Vec::new(), - }); - - Role::Primary - }, - Entry::Occupied(mut occupied) => { - occupied.get_mut().backup.push(collator_id); - - Role::Backup - } - } - } - - /// Called when a collator disconnects. If it was the primary, returns a new primary for that - /// parachain. - pub fn on_disconnect(&mut self, collator_id: CollatorId) -> Option { - self.collators.remove(&collator_id).and_then(|(para_id, _)| match self.parachain_collators.entry(para_id) { - Entry::Vacant(_) => None, - Entry::Occupied(mut occ) => { - if occ.get().primary == collator_id { - if occ.get().backup.is_empty() { - occ.remove(); - None - } else { - let mut collators = occ.get_mut(); - collators.primary = collators.backup.pop().expect("backup non-empty; qed"); - Some(collators.primary.clone()) - } - } else { - let pos = occ.get().backup.iter().position(|a| a == &collator_id) - .expect("registered collator always present in backup if not primary; qed"); - - occ.get_mut().backup.remove(pos); - None - } - } - }) - } - - /// Called when a collation is received. - /// The collator should be registered for the parachain of the collation as a precondition of this function. - /// The collation should have been checked for integrity of signature before passing to this function. - pub fn on_collation(&mut self, collator_id: CollatorId, relay_parent: Hash, collation: Collation) { - log::debug!( - target: "collator-pool", "On collation from collator {} for relay parent {}", - collator_id, - relay_parent, - ); - - if let Some((para_id, _)) = self.collators.get(&collator_id) { - debug_assert_eq!(para_id, &collation.info.parachain_index); - - // TODO: punish if not primary? (https://github.com/paritytech/polkadot/issues/213) - - self.collations.entry((relay_parent, para_id.clone())) - .or_insert_with(CollationSlot::blank_now) - .entries - .received_collation(collation); - } - } - - /// Wait for a collation from a parachain. - pub fn await_collation(&mut self, relay_parent: Hash, para_id: ParaId, sender: oneshot::Sender) { - self.collations.entry((relay_parent, para_id)) - .or_insert_with(CollationSlot::blank_now) - .entries - .await_with(sender); - } - - /// Call periodically to perform collator set maintenance. - /// Returns a set of actions to perform on the network level. - pub fn maintain_peers(&mut self) -> Vec { - // TODO: rearrange periodically to new primary, evaluate based on latency etc. - // https://github.com/paritytech/polkadot/issues/214 - Vec::new() - } - - /// called when a block with given hash has been imported. - pub fn collect_garbage(&mut self, chain_head: Option<&Hash>) { - let now = Instant::now(); - self.collations.retain(|&(ref h, _), slot| chain_head != Some(h) && slot.stay_alive(now)); - } - - /// Convert the given `CollatorId` to a `PeerId`. - pub fn collator_id_to_peer_id(&self, collator_id: &CollatorId) -> Option<&PeerId> { - self.collators.get(collator_id).map(|ids| &ids.1) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::crypto::UncheckedInto; - use polkadot_primitives::v0::{CollationInfo, BlockData, PoVBlock}; - use futures::executor::block_on; - - fn make_pov(block_data: Vec) -> PoVBlock { - PoVBlock { - block_data: BlockData(block_data), - } - } - - #[test] - fn disconnect_primary_gives_new_primary() { - let mut pool = CollatorPool::new(); - let para_id: ParaId = 5.into(); - let bad_primary: CollatorId = [0; 32].unchecked_into(); - let good_backup: CollatorId = [1; 32].unchecked_into(); - - assert_eq!(pool.on_new_collator(bad_primary.clone(), para_id.clone(), PeerId::random()), Role::Primary); - assert_eq!(pool.on_new_collator(good_backup.clone(), para_id.clone(), PeerId::random()), Role::Backup); - assert_eq!(pool.on_disconnect(bad_primary), Some(good_backup.clone())); - assert_eq!(pool.on_disconnect(good_backup), None); - } - - #[test] - fn disconnect_backup_removes_from_pool() { - let mut pool = CollatorPool::new(); - let para_id: ParaId = 5.into(); - let primary = [0; 32].unchecked_into(); - let backup: CollatorId = [1; 32].unchecked_into(); - - assert_eq!(pool.on_new_collator(primary, para_id.clone(), PeerId::random()), Role::Primary); - assert_eq!(pool.on_new_collator(backup.clone(), para_id.clone(), PeerId::random()), Role::Backup); - assert_eq!(pool.on_disconnect(backup), None); - assert!(pool.parachain_collators.get(¶_id).unwrap().backup.is_empty()); - } - - #[test] - fn await_before_collation() { - let mut pool = CollatorPool::new(); - let para_id: ParaId = 5.into(); - let peer_id = PeerId::random(); - let primary: CollatorId = [0; 32].unchecked_into(); - let relay_parent = [1; 32].into(); - - assert_eq!(pool.on_new_collator(primary.clone(), para_id.clone(), peer_id.clone()), Role::Primary); - let (tx1, rx1) = oneshot::channel(); - let (tx2, rx2) = oneshot::channel(); - pool.await_collation(relay_parent, para_id, tx1); - pool.await_collation(relay_parent, para_id, tx2); - let mut collation_info = CollationInfo::default(); - collation_info.parachain_index = para_id; - collation_info.collator = primary.clone().into(); - pool.on_collation(primary.clone(), relay_parent, Collation { - info: collation_info, - pov: make_pov(vec![4, 5, 6]), - }); - - block_on(rx1).unwrap(); - block_on(rx2).unwrap(); - assert_eq!(pool.collators.get(&primary).map(|ids| &ids.1).unwrap(), &peer_id); - } - - #[test] - fn collate_before_await() { - let mut pool = CollatorPool::new(); - let para_id: ParaId = 5.into(); - let primary: CollatorId = [0; 32].unchecked_into(); - let relay_parent = [1; 32].into(); - - assert_eq!(pool.on_new_collator(primary.clone(), para_id.clone(), PeerId::random()), Role::Primary); - - let mut collation_info = CollationInfo::default(); - collation_info.parachain_index = para_id; - collation_info.collator = primary.clone(); - pool.on_collation(primary.clone(), relay_parent, Collation { - info: collation_info, - pov: make_pov(vec![4, 5, 6]), - }); - - let (tx, rx) = oneshot::channel(); - pool.await_collation(relay_parent, para_id, tx); - block_on(rx).unwrap(); - } - - #[test] - fn slot_stay_alive() { - let slot = CollationSlot::blank_now(); - let now = slot.live_at; - - assert!(slot.stay_alive(now)); - assert!(slot.stay_alive(now + Duration::from_secs(10))); - assert!(!slot.stay_alive(now + COLLATION_LIFETIME)); - assert!(!slot.stay_alive(now + COLLATION_LIFETIME + Duration::from_secs(10))); - } -} diff --git a/network/src/legacy/gossip/attestation.rs b/network/src/legacy/gossip/attestation.rs deleted file mode 100644 index 2d20ce63b995..000000000000 --- a/network/src/legacy/gossip/attestation.rs +++ /dev/null @@ -1,347 +0,0 @@ -// Copyright 2019-2020 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 . - -//! Gossip messages and structures for dealing with attestations (statements of -//! validity of invalidity on parachain candidates). -//! -//! This follows the same principles as other gossip modules (see parent -//! documentation for more details) by being aware of our current chain -//! heads and accepting only information relative to them. Attestations are localized to -//! relay chain head, so this is easily doable. -//! -//! This module also provides a filter, so we can only broadcast messages to -//! peers that are relevant to chain heads they have advertised. -//! -//! Furthermore, since attestations are bottlenecked by the `Candidate` statement, -//! we only accept attestations which are themselves `Candidate` messages, or reference -//! a `Candidate` we are aware of. Otherwise, it is possible we could be forced to -//! consider an infinite amount of attestations produced by a misbehaving validator. - -use sc_network_gossip::{ValidationResult as GossipValidationResult}; -use sc_network::ReputationChange; -use polkadot_validation::GenericStatement; -use polkadot_primitives::v0::Hash; - -use std::collections::HashMap; - -use log::warn; - -use super::{ - cost, benefit, attestation_topic, MAX_CHAIN_HEADS, LeavesVec, - 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: 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_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, candidate_meta: CandidateMeta) { - self.candidates.insert(candidate_hash, candidate_meta); - } -} - -#[derive(Default)] -pub(super) struct PeerData { - live: HashMap, -} - -impl PeerData { - /// Update leaves, returning a list of which leaves are new. - pub(super) fn update_leaves(&mut self, leaves: &LeavesVec) -> LeavesVec { - let mut new = LeavesVec::new(); - self.live.retain(|k, _| leaves.contains(k)); - for &leaf in leaves { - self.live.entry(leaf).or_insert_with(|| { - new.push(leaf); - Default::default() - }); - } - - new - } - - #[cfg(test)] - 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, meta); - } - } - - pub(super) fn knowledge_at_mut(&mut self, parent_hash: &Hash) -> Option<&mut Knowledge> { - self.live.get_mut(parent_hash) - } -} - -/// An impartial view of what topics and data are valid based on attestation session data. -pub(super) struct View { - leaf_work: Vec<(Hash, LeafView)>, // hashes of the best DAG-leaves paired with validation data. - topics: HashMap, // maps topic hashes to block hashes. -} - -impl Default for View { - fn default() -> Self { - View { - leaf_work: Vec::with_capacity(MAX_CHAIN_HEADS), - topics: Default::default(), - } - } -} - -impl View { - fn leaf_view(&self, relay_chain_leaf: &Hash) -> Option<&LeafView> { - self.leaf_work.iter() - .find_map(|&(ref h, ref leaf)| if h == relay_chain_leaf { Some(leaf) } else { None } ) - } - - fn leaf_view_mut(&mut self, relay_chain_leaf: &Hash) -> Option<&mut LeafView> { - self.leaf_work.iter_mut() - .find_map(|&mut (ref h, ref mut leaf)| if h == relay_chain_leaf { Some(leaf) } else { None } ) - } - - /// Get our leaves-set. Guaranteed to have length <= MAX_CHAIN_HEADS. - pub(super) fn neighbor_info<'a>(&'a self) -> impl Iterator + 'a + Clone { - self.leaf_work.iter().take(MAX_CHAIN_HEADS).map(|(p, _)| p.clone()) - } - - /// Note new leaf in our local view and validation data necessary to check signatures - /// of statements issued under this leaf. - /// - /// This will be pruned later on a call to `prune_old_leaves`, when this leaf - /// is not a leaf anymore. - pub(super) fn new_local_leaf( - &mut self, - validation_data: MessageValidationData, - ) { - let relay_chain_leaf = validation_data.signing_context.parent_hash.clone(); - self.leaf_work.push(( - validation_data.signing_context.parent_hash.clone(), - LeafView { - validation_data, - knowledge: Default::default(), - }, - )); - 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. - pub(super) fn prune_old_leaves bool>(&mut self, is_leaf: F) { - let leaf_work = &mut self.leaf_work; - leaf_work.retain(|&(ref relay_chain_leaf, _)| is_leaf(relay_chain_leaf)); - self.topics.retain(|_, v| leaf_work.iter().find(|(p, _)| p == v).is_some()); - } - - /// Whether a message topic is considered live relative to our view. non-live - /// topics do not pertain to our perceived leaves, and are uninteresting to us. - pub(super) fn is_topic_live(&self, topic: &Hash) -> bool { - self.topics.contains_key(topic) - } - - /// The relay-chain block hash corresponding to a topic. - pub(super) fn topic_block(&self, topic: &Hash) -> Option<&Hash> { - 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. - pub(super) fn validate_statement_signature( - &mut self, - message: GossipStatement, - chain: &C, - ) - -> (GossipValidationResult, ReputationChange) - { - // message must reference one of our chain heads and - // if message is not a `Candidate` we should have the candidate available - // in `attestation_view`. - 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::PAST_MESSAGE, - _ => cost::FUTURE_MESSAGE, - }; - - (GossipValidationResult::Discard, cost) - } - Some(view) => { - // first check that we are capable of receiving this message - // in a DoS-proof manner. - let benefit = match message.signed_statement.statement { - GenericStatement::Candidate(_) => benefit::NEW_CANDIDATE, - GenericStatement::Valid(ref h) | GenericStatement::Invalid(ref h) => { - if !view.knowledge.is_aware_of(h) { - let cost = cost::ATTESTATION_NO_CANDIDATE; - return (GossipValidationResult::Discard, cost); - } - - benefit::NEW_ATTESTATION - } - }; - - // validate signature. - let res = view.validation_data.check_statement( - &message.signed_statement, - ); - - match res { - Ok(()) => { - let topic = attestation_topic(message.relay_chain_leaf); - (GossipValidationResult::ProcessAndKeep(topic), benefit) - } - Err(()) => (GossipValidationResult::Discard, cost::BAD_SIGNATURE), - } - } - } - } - - /// 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, - 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) => { - // `valid` and `invalid` statements can only be propagated after - // a candidate message is known by that peer. - peer_knowledge.is_aware_of(h) - } - GenericStatement::Candidate(ref c) => { - // 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(); - 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, meta); - } - - // at this point, the peer hasn't seen the message or the candidate - // and has knowledge of the relevant relay-chain parent. - true - } - } - } - - /// 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 { - validation_data: MessageValidationData, - knowledge: Knowledge, -} diff --git a/network/src/legacy/gossip/mod.rs b/network/src/legacy/gossip/mod.rs deleted file mode 100644 index 9e18d7ce2171..000000000000 --- a/network/src/legacy/gossip/mod.rs +++ /dev/null @@ -1,1249 +0,0 @@ -// Copyright 2019-2020 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 . - -//! Gossip messages and the message validator. -//! -//! At the moment, this module houses 2 gossip protocols central to Polkadot. -//! -//! The first is the attestation-gossip system, which aims to circulate parachain -//! candidate attestations by validators at leaves of the block-DAG. -//! -//! The second is the inter-chain message queue routing gossip, which aims to -//! circulate message queues between parachains, which remain un-routed as of -//! recent leaves. -//! -//! These gossip systems do not have any form of sybil-resistance in terms -//! of the nodes which can participate. It could be imposed e.g. by limiting only to -//! validators, but this would prevent message queues from getting into the hands -//! of collators and of attestations from getting into the hands of fishermen. -//! As such, we take certain precautions which allow arbitrary full nodes to -//! join the gossip graph, as well as validators (who are likely to be well-connected -//! amongst themselves). -//! -//! The first is the notion of a neighbor packet. This is a packet sent between -//! neighbors of the gossip graph to inform each other of their current protocol -//! state. As of this writing, for both attestation and message-routing gossip, -//! the only necessary information here is a (length-limited) set of perceived -//! leaves of the block-DAG. -//! -//! These leaves can be used to derive what information a node is willing to accept -//! There is typically an unbounded amount of possible "future" information relative to -//! any protocol state. For example, attestations or unrouted message queues from millions -//! of blocks after a known protocol state. The neighbor packet is meant to avoid being -//! spammed by illegitimate future information, while informing neighbors of when -//! previously-future and now current gossip messages would be accepted. -//! -//! Peers who send information which was not allowed under a recent neighbor packet -//! will be noted as non-beneficial to Substrate's peer-set management utility. - -use sp_runtime::traits::{BlakeTwo256, Hash as HashT}; -use sp_blockchain::Error as ClientError; -use sc_network::{ObservedRole, PeerId, ReputationChange}; -use sc_network::NetworkService; -use sc_network_gossip::{ - ValidationResult as GossipValidationResult, - ValidatorContext, MessageIntent, -}; -use polkadot_validation::{SignedStatement}; -use polkadot_primitives::v0::{ - Block, Hash, - ParachainHost, ValidatorId, ErasureChunk as PrimitiveChunk, SigningContext, PoVBlock, -}; -use polkadot_erasure_coding::{self as erasure}; -use codec::{Decode, Encode}; -use sp_api::ProvideRuntimeApi; - -use std::collections::HashMap; -use std::sync::Arc; - -use arrayvec::ArrayVec; -use futures::prelude::*; -use parking_lot::{Mutex, RwLock}; - -use crate::legacy::{GossipMessageStream, GossipService}; - -use attestation::{View as AttestationView, PeerData as AttestationPeerData}; - -mod attestation; - -/// The engine ID of the polkadot attestation system. -pub const POLKADOT_ENGINE_ID: sp_runtime::ConsensusEngineId = *b"dot1"; -pub const POLKADOT_PROTOCOL_NAME: &[u8] = b"/polkadot/legacy/1"; - -// arbitrary; in practice this should not be more than 2. -pub(crate) const MAX_CHAIN_HEADS: usize = 5; - -/// Type alias for a bounded vector of leaves. -pub type LeavesVec = ArrayVec<[Hash; MAX_CHAIN_HEADS]>; - -mod benefit { - use sc_network::ReputationChange as Rep; - /// When a peer sends us a previously-unknown candidate statement. - 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"); -} - -mod cost { - use sc_network::ReputationChange as Rep; - /// No cost. This will not be reported. - 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. - pub const PAST_MESSAGE: Rep = Rep::new(-30, "Polkadot: Past message"); - /// A peer sent us a malformed message. - pub const MALFORMED_MESSAGE: Rep = Rep::new(-500, "Polkadot: Malformed message"); - /// A peer sent us a wrongly signed message. - pub const BAD_SIGNATURE: Rep = Rep::new(-500, "Polkadot: Bad signature"); - /// A peer sent us a bad neighbor packet. - pub const BAD_NEIGHBOR_PACKET: Rep = Rep::new(-300, "Polkadot: Bad neighbor"); - /// A peer sent us an erasure chunk referring to a candidate that we are not aware of. - pub const ORPHANED_ERASURE_CHUNK: Rep = Rep::new(-10, "An erasure chunk from unknown candidate"); - /// A peer sent us an erasure chunk that does not match candidate's erasure root. - pub const ERASURE_CHUNK_WRONG_ROOT: Rep = Rep::new(-100, "Chunk doesn't match encoding root"); -} - -/// A gossip message. -#[derive(Encode, Decode, Clone, PartialEq)] -pub enum GossipMessage { - /// A packet sent to a neighbor but not relayed. - #[codec(index = "1")] - Neighbor(VersionedNeighborPacket), - /// An attestation-statement about the candidate. - /// Non-candidate statements should only be sent to peers who are aware of the candidate. - #[codec(index = "2")] - Statement(GossipStatement), - // TODO: https://github.com/paritytech/polkadot/issues/253 - /// 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 { - fn from(packet: NeighborPacket) -> Self { - GossipMessage::Neighbor(VersionedNeighborPacket::V1(packet)) - } -} - -impl From for GossipMessage { - fn from(stmt: GossipStatement) -> Self { - GossipMessage::Statement(stmt) - } -} - -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 { - /// The block hash of the relay chain being referred to. In context, this should - /// be a leaf. - pub relay_chain_leaf: Hash, - /// The signed statement being gossipped. - pub signed_statement: SignedStatement, -} - -impl GossipStatement { - /// Create a new instance. - pub fn new(relay_chain_leaf: Hash, signed_statement: SignedStatement) -> Self { - Self { - relay_chain_leaf, - signed_statement, - } - } -} - -/// A gossip message containing one erasure chunk of a candidate block. -/// For each chunk of block erasure encoding one of this messages is constructed. -#[derive(Encode, Decode, Clone, Debug, PartialEq)] -pub struct ErasureChunkMessage { - /// The chunk itself. - pub chunk: PrimitiveChunk, - /// The hash of the candidate receipt of the block this chunk belongs to. - pub candidate_hash: Hash, -} - -impl From for GossipMessage { - fn from(chk: ErasureChunkMessage) -> Self { - GossipMessage::ErasureChunk(chk) - } -} - -/// 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. -#[derive(Encode, Decode, Clone, PartialEq)] -pub enum VersionedNeighborPacket { - #[codec(index = "1")] - V1(NeighborPacket), -} - -/// Contains information on which chain heads the peer is -/// accepting messages for. -#[derive(Encode, Decode, Clone, PartialEq)] -pub struct NeighborPacket { - chain_heads: Vec, -} - -/// whether a block is known. -#[derive(Clone, Copy, PartialEq)] -pub enum Known { - /// The block is a known leaf. - Leaf, - /// The block is known to be old. - Old, - /// The block is known to be bad. - Bad, -} - -/// Context to the underlying polkadot chain. -pub trait ChainContext: Send + Sync { - /// Provide a closure which is invoked for every unrouted queue hash at a given leaf. - fn leaf_unrouted_roots( - &self, - leaf: &Hash, - with_queue_root: &mut dyn FnMut(&Hash), - ) -> Result<(), ClientError>; - - /// whether a block is known. If it's not, returns `None`. - fn is_known(&self, block_hash: &Hash) -> Option; -} - -impl ChainContext for (F, P) where - F: Fn(&Hash) -> Option + Send + Sync, - P: Send + Sync + std::ops::Deref, - P::Target: ProvideRuntimeApi, - >::Api: ParachainHost, -{ - fn is_known(&self, block_hash: &Hash) -> Option { - (self.0)(block_hash) - } - - fn leaf_unrouted_roots( - &self, - _leaf: &Hash, - _with_queue_root: &mut dyn FnMut(&Hash), - ) -> Result<(), ClientError> { - Ok(()) - } -} - - -/// Compute the gossip topic for attestations on the given parent hash. -pub(crate) fn attestation_topic(parent_hash: Hash) -> Hash { - let mut v = parent_hash.as_ref().to_vec(); - v.extend(b"attestations"); - - 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 -// to construct it outside of tests. -pub fn register_validator( - service: Arc>, - chain: C, - executor: &impl sp_core::traits::SpawnNamed, -) -> RegisteredMessageValidator -{ - let s = service.clone(); - let report_handle = Box::new(move |peer: &PeerId, cost_benefit: ReputationChange| { - if cost_benefit.value != 0 { - s.report_peer(peer.clone(), cost_benefit); - } - }); - let validator = Arc::new(MessageValidator { - report_handle, - inner: RwLock::new(Inner { - peers: HashMap::new(), - attestation_view: Default::default(), - availability_store: None, - chain, - }) - }); - - let gossip_side = validator.clone(); - let gossip_engine = Arc::new(Mutex::new(sc_network_gossip::GossipEngine::new( - service.clone(), - POLKADOT_ENGINE_ID, - POLKADOT_PROTOCOL_NAME, - gossip_side, - ))); - - // Spawn gossip engine. - // - // Ideally this would not be spawned as an orphaned task, but polled by - // `RegisteredMessageValidator` which in turn would be polled by a `ValidationNetwork`. - { - let gossip_engine = gossip_engine.clone(); - let fut = futures::future::poll_fn(move |cx| { - gossip_engine.lock().poll_unpin(cx) - }); - executor.spawn("polkadot-legacy-gossip-engine", fut.boxed()); - } - - RegisteredMessageValidator { - inner: validator as _, - service: Some(service), - gossip_engine: Some(gossip_engine), - } -} - -#[derive(PartialEq)] -enum NewLeafAction { - // (who, message) - TargetedMessage(PeerId, GossipMessage), -} - -/// Actions to take after noting a new block-DAG leaf. -/// -/// This should be consumed by passing a consensus-gossip handle to `perform`. -#[must_use = "New chain-head gossip actions must be performed"] -pub struct NewLeafActions { - actions: Vec, -} - -impl NewLeafActions { - #[cfg(test)] - pub fn new() -> Self { - NewLeafActions { actions: Vec::new() } - } - - /// Perform the queued actions, feeding into gossip. - pub fn perform( - self, - gossip: &dyn crate::legacy::GossipService, - ) { - for action in self.actions { - match action { - NewLeafAction::TargetedMessage(who, message) - => gossip.send_message(who, message), - } - } - } -} - -/// A registered message validator. -/// -/// Create this using `register_validator`. -#[derive(Clone)] -pub struct RegisteredMessageValidator { - inner: Arc>, - // Note: this is always `Some` in real code and `None` in tests. - service: Option>>, - // Note: this is always `Some` in real code and `None` in tests. - gossip_engine: Option>>>, -} - -impl RegisteredMessageValidator { - /// Register an availabilty store the gossip service can query. - pub(crate) fn register_availability_store(&self, availability_store: av_store::Store) { - self.inner.inner.write().availability_store = Some(availability_store); - } - - /// Note that we perceive a new leaf of the block-DAG. We will notify our neighbors that - /// we now accept parachain candidate attestations and incoming message queues - /// relevant to this leaf. - pub(crate) fn new_local_leaf( - &self, - validation: MessageValidationData, - ) -> NewLeafActions { - // add an entry in attestation_view - // prune any entries from attestation_view which are no longer leaves - let mut inner = self.inner.inner.write(); - inner.attestation_view.new_local_leaf(validation); - - let mut actions = Vec::new(); - - { - let &mut Inner { - ref chain, - ref mut attestation_view, - .. - } = &mut *inner; - - attestation_view.prune_old_leaves(|hash| match chain.is_known(hash) { - Some(Known::Leaf) => true, - _ => false, - }); - } - - - // send neighbor packets to peers - inner.multicast_neighbor_packet( - |who, message| actions.push(NewLeafAction::TargetedMessage(who.clone(), message)) - ); - - NewLeafActions { actions } - } - - pub(crate) fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream { - let topic_stream = if let Some(gossip_engine) = self.gossip_engine.as_ref() { - gossip_engine.lock().messages_for(topic) - } else { - log::error!("Called gossip_messages_for on a test engine"); - futures::channel::mpsc::channel(0).1 - }; - - GossipMessageStream::new(topic_stream.boxed()) - } - - pub(crate) fn gossip_message(&self, topic: Hash, message: GossipMessage) { - if let Some(gossip_engine) = self.gossip_engine.as_ref() { - gossip_engine.lock().gossip_message( - topic, - message.encode(), - false, - ); - } else { - log::error!("Called gossip_message on a test engine"); - } - } - - pub(crate) fn send_message(&self, who: PeerId, message: GossipMessage) { - if let Some(gossip_engine) = self.gossip_engine.as_ref() { - gossip_engine.lock().send_message(vec![who], message.encode()); - } else { - log::error!("Called send_message on a test engine"); - } - } -} - -impl GossipService for RegisteredMessageValidator { - fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream { - RegisteredMessageValidator::gossip_messages_for(self, topic) - } - - fn gossip_message(&self, topic: Hash, message: GossipMessage) { - RegisteredMessageValidator::gossip_message(self, topic, message) - } - - fn send_message(&self, who: PeerId, message: GossipMessage) { - RegisteredMessageValidator::send_message(self, who, message) - } -} - -/// The data needed for validating gossip messages. -#[derive(Default)] -pub(crate) struct MessageValidationData { - /// The authorities' parachain validation keys at a block. - pub(crate) authorities: Vec, - /// The signing context. - pub(crate) signing_context: SigningContext, -} - -impl MessageValidationData { - // check a statement's signature. - fn check_statement(&self, statement: &SignedStatement) -> Result<(), ()> { - let sender = match self.authorities.get(statement.sender as usize) { - Some(val) => val, - None => return Err(()), - }; - - let good = self.authorities.contains(&sender) && - ::polkadot_validation::check_statement( - &statement.statement, - &statement.signature, - sender.clone(), - &self.signing_context, - ); - - if good { - Ok(()) - } else { - Err(()) - } - } -} - -#[derive(Default)] -struct PeerData { - attestation: AttestationPeerData, -} - -struct Inner { - peers: HashMap, - attestation_view: AttestationView, - availability_store: Option, - chain: C, -} - -impl Inner { - fn validate_neighbor_packet(&mut self, sender: &PeerId, packet: NeighborPacket) - -> (GossipValidationResult, ReputationChange, Vec) - { - let chain_heads = packet.chain_heads; - if chain_heads.len() > MAX_CHAIN_HEADS { - (GossipValidationResult::Discard, cost::BAD_NEIGHBOR_PACKET, Vec::new()) - } else { - let chain_heads: LeavesVec = chain_heads.into_iter().collect(); - 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.chain(new_pov_block_topics).collect() - } else { - Vec::new() - }; - - (GossipValidationResult::Discard, cost::NONE, new_topics) - } - } - - fn validate_erasure_chunk_packet(&mut self, msg: ErasureChunkMessage) - -> (GossipValidationResult, ReputationChange) - { - if let Some(store) = &self.availability_store { - if let Some(receipt) = store.get_candidate(&msg.candidate_hash) { - let chunk_hash = erasure::branch_hash( - &receipt.commitments.erasure_root, - &msg.chunk.proof, - msg.chunk.index as usize - ); - - if chunk_hash != Ok(BlakeTwo256::hash(&msg.chunk.chunk)) { - ( - GossipValidationResult::Discard, - cost::ERASURE_CHUNK_WRONG_ROOT - ) - } else { - if let Some(awaited_chunks) = store.awaited_chunks() { - let frontier_entry = av_store::AwaitedFrontierEntry { - candidate_hash: msg.candidate_hash, - relay_parent: receipt.relay_parent, - validator_index: msg.chunk.index, - }; - if awaited_chunks.contains(&frontier_entry) { - let topic = crate::erasure_coding_topic( - &msg.candidate_hash - ); - - return ( - GossipValidationResult::ProcessAndKeep(topic), - benefit::NEW_ERASURE_CHUNK, - ); - } - } - (GossipValidationResult::Discard, cost::NONE) - } - } else { - (GossipValidationResult::Discard, cost::ORPHANED_ERASURE_CHUNK) - } - } else { - (GossipValidationResult::Discard, cost::NONE) - } - } - - fn multicast_neighbor_packet( - &self, - mut send_neighbor_packet: F, - ) { - let neighbor_packet = GossipMessage::from(NeighborPacket { - chain_heads: self.attestation_view.neighbor_info().collect(), - }); - - for peer in self.peers.keys() { - send_neighbor_packet(peer, neighbor_packet.clone()) - } - } -} - -/// An unregistered message validator. Register this with `register_validator`. -pub struct MessageValidator { - report_handle: Box, - inner: RwLock>, -} - -impl MessageValidator { - #[cfg(test)] - fn new_test( - chain: C, - report_handle: Box, - ) -> Self where C: Sized { - MessageValidator { - report_handle, - inner: RwLock::new(Inner { - peers: HashMap::new(), - attestation_view: Default::default(), - availability_store: None, - chain, - }), - } - } - - fn report(&self, who: &PeerId, cost_benefit: ReputationChange) { - (self.report_handle)(who, cost_benefit) - } -} - -impl sc_network_gossip::Validator for MessageValidator { - fn new_peer(&self, _context: &mut dyn ValidatorContext, who: &PeerId, _role: ObservedRole) { - let mut inner = self.inner.write(); - inner.peers.insert(who.clone(), PeerData::default()); - } - - fn peer_disconnected(&self, _context: &mut dyn ValidatorContext, who: &PeerId) { - let mut inner = self.inner.write(); - inner.peers.remove(who); - } - - fn validate(&self, context: &mut dyn ValidatorContext, sender: &PeerId, data: &[u8]) - -> GossipValidationResult - { - let mut decode_data = data; - let (res, cost_benefit) = match GossipMessage::decode(&mut decode_data) { - Err(_) => (GossipValidationResult::Discard, cost::MALFORMED_MESSAGE), - Ok(GossipMessage::Neighbor(VersionedNeighborPacket::V1(packet))) => { - let (res, cb, topics) = self.inner.write().validate_neighbor_packet(sender, packet); - for new_topic in topics { - context.send_topic(sender, new_topic, false); - } - (res, cb) - } - Ok(GossipMessage::Statement(statement)) => { - let (res, cb) = { - let mut inner = self.inner.write(); - let inner = &mut *inner; - inner.attestation_view.validate_statement_signature(statement, &inner.chain) - }; - - if let GossipValidationResult::ProcessAndKeep(ref topic) = res { - context.broadcast_message(topic.clone(), data.to_vec(), false); - } - (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) - } - }; - - self.report(sender, cost_benefit); - res - } - - fn message_expired<'a>(&'a self) -> Box bool + 'a> { - let inner = self.inner.read(); - - Box::new(move |topic, _data| { - // check that messages from this topic are considered live by one of our protocols. - // everything else is expired - let live = inner.attestation_view.is_topic_live(&topic); - - !live // = expired - }) - } - - fn message_allowed<'a>(&'a self) -> Box bool + 'a> { - let mut inner = self.inner.write(); - Box::new(move |who, intent, topic, data| { - let &mut Inner { - ref mut peers, - ref mut attestation_view, - .. - } = &mut *inner; - - match intent { - MessageIntent::PeriodicRebroadcast => return false, - _ => {}, - } - - let attestation_head = attestation_view.topic_block(topic).map(|x| x.clone()); - let peer = peers.get_mut(who); - - match GossipMessage::decode(&mut &data[..]) { - Ok(GossipMessage::Statement(ref statement)) => { - // to allow statements, 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)| { - 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, - } - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sc_network_gossip::Validator as ValidatorT; - use std::sync::mpsc; - use parking_lot::Mutex; - use polkadot_primitives::v0::{AbridgedCandidateReceipt, BlockData}; - use sp_core::sr25519::Signature as Sr25519Signature; - use polkadot_validation::GenericStatement; - - #[derive(PartialEq, Clone, Debug)] - enum ContextEvent { - BroadcastTopic(Hash, bool), - BroadcastMessage(Hash, Vec, bool), - SendMessage(PeerId, Vec), - SendTopic(PeerId, Hash, bool), - } - - #[derive(Default)] - struct MockValidatorContext { - events: Vec, - } - - impl MockValidatorContext { - fn clear(&mut self) { - self.events.clear() - } - } - - impl sc_network_gossip::ValidatorContext for MockValidatorContext { - fn broadcast_topic(&mut self, topic: Hash, force: bool) { - self.events.push(ContextEvent::BroadcastTopic(topic, force)); - } - fn broadcast_message(&mut self, topic: Hash, message: Vec, force: bool) { - self.events.push(ContextEvent::BroadcastMessage(topic, message, force)); - } - fn send_message(&mut self, who: &PeerId, message: Vec) { - self.events.push(ContextEvent::SendMessage(who.clone(), message)); - } - fn send_topic(&mut self, who: &PeerId, topic: Hash, force: bool) { - self.events.push(ContextEvent::SendTopic(who.clone(), topic, force)); - } - } - - #[derive(Default)] - struct TestChainContext { - known_map: HashMap, - ingress_roots: HashMap>, - } - - impl ChainContext for TestChainContext { - fn is_known(&self, block_hash: &Hash) -> Option { - self.known_map.get(block_hash).map(|x| x.clone()) - } - - fn leaf_unrouted_roots(&self, leaf: &Hash, with_queue_root: &mut dyn FnMut(&Hash)) - -> Result<(), sp_blockchain::Error> - { - for root in self.ingress_roots.get(leaf).into_iter().flat_map(|roots| roots) { - with_queue_root(root) - } - - Ok(()) - } - } - - #[test] - 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()); - 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, ObservedRole::Full); - assert!(validator_context.events.is_empty()); - validator_context.clear(); - - let hash_a = [1u8; 32].into(); - let hash_b = [2u8; 32].into(); - let hash_c = [3u8; 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 candidate_receipt = AbridgedCandidateReceipt::default(); - let statement = GossipMessage::Statement(GossipStatement { - relay_chain_leaf: hash_a, - signed_statement: SignedStatement { - statement: GenericStatement::Candidate(candidate_receipt), - signature: Sr25519Signature([255u8; 64]).into(), - sender: 1, - } - }); - let encoded = statement.encode(); - - let topic_a = attestation_topic(hash_a); - let topic_b = attestation_topic(hash_b); - let topic_c = attestation_topic(hash_c); - - // topic_a is in all 3 views -> succeed - let mut validation_data = MessageValidationData::default(); - validation_data.signing_context.parent_hash = hash_a; - validator.inner.write().attestation_view.new_local_leaf(validation_data); - // topic_b is in the neighbor's view but not ours -> fail - // topic_c is not in either -> fail - - { - let mut message_allowed = validator.message_allowed(); - let intent = MessageIntent::Broadcast; - assert!(message_allowed(&peer_a, intent, &topic_a, &encoded)); - assert!(!message_allowed(&peer_a, intent, &topic_b, &encoded)); - assert!(!message_allowed(&peer_a, intent, &topic_c, &encoded)); - } - } - - #[test] - fn too_many_chain_heads_is_report() { - 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, ObservedRole::Full); - assert!(validator_context.events.is_empty()); - validator_context.clear(); - - let chain_heads = (0..MAX_CHAIN_HEADS+1).map(|i| [i as u8; 32].into()).collect(); - - let message = GossipMessage::from(NeighborPacket { - chain_heads, - }).encode(); - let res = validator.validate( - &mut validator_context, - &peer_a, - &message[..], - ); - - match res { - GossipValidationResult::Discard => {}, - _ => panic!("wrong result"), - } - assert_eq!( - validator_context.events, - Vec::new(), - ); - - drop(validator); - - assert_eq!(rx.iter().collect::>(), vec![(peer_a, cost::BAD_NEIGHBOR_PACKET)]); - } - - #[test] - fn statement_only_sent_when_candidate_known() { - 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, ObservedRole::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 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, ObservedRole::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 topic_a = pov_block_topic(hash_a); - let c_hash = [99u8; 32].into(); - - 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, - candidate_hash: c_hash, - pov_block, - }); - 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); - - { - let mut message_allowed = validator.message_allowed(); - assert!(!message_allowed(&peer_a, MessageIntent::Broadcast, &topic_a, &encoded[..])); - } - - validator - .inner - .write() - .peers - .get_mut(&peer_a) - .unwrap() - .attestation - .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, ObservedRole::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/legacy/local_collations.rs b/network/src/legacy/local_collations.rs deleted file mode 100644 index d85911548613..000000000000 --- a/network/src/legacy/local_collations.rs +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2018-2020 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 . - -//! Local collations to be circulated to validators. -//! -//! Collations are attempted to be repropagated when a new validator connects, -//! a validator changes his session key, or when they are generated. - -use polkadot_primitives::v0::{Hash, ValidatorId}; -use crate::legacy::collator_pool::Role; -use std::collections::{HashMap, HashSet}; -use std::time::Duration; -use wasm_timer::Instant; -use rand::seq::SliceRandom; - -const LIVE_FOR: Duration = Duration::from_secs(60 * 5); - -struct LocalCollation { - targets: HashSet, - collation: C, - live_since: Instant, -} - -/// Tracker for locally collated values and which validators to send them to. -pub struct LocalCollations { - primary_for: HashSet, - local_collations: HashMap>, -} - -impl Default for LocalCollations { - fn default() -> Self { - Self::new() - } -} - -impl LocalCollations { - /// Create a new `LocalCollations` tracker. - pub fn new() -> Self { - LocalCollations { - primary_for: HashSet::new(), - local_collations: HashMap::new(), - } - } - - /// Validator gave us a new role. If the new role is "primary", this function might return - /// a set of collations to send to that validator. - pub fn note_validator_role(&mut self, key: ValidatorId, role: Role) -> Vec<(Hash, C)> { - match role { - Role::Backup => { - self.primary_for.remove(&key); - Vec::new() - } - Role::Primary => { - let new_primary = self.primary_for.insert(key.clone()); - if new_primary { - self.collations_targeting(&key) - } else { - Vec::new() - } - } - } - } - - /// Fresh session key from a validator. Returns a vector of collations to send - /// to the validator. - pub fn fresh_key(&mut self, old_key: &ValidatorId, new_key: &ValidatorId) -> Vec<(Hash, C)> { - if self.primary_for.remove(old_key) { - self.primary_for.insert(new_key.clone()); - - self.collations_targeting(new_key) - } else { - Vec::new() - } - } - - /// Validator disconnected. - pub fn on_disconnect(&mut self, key: &ValidatorId) { - self.primary_for.remove(key); - } - - /// Mark collations relevant to the given parent hash as obsolete. - pub fn collect_garbage(&mut self, relay_parent: Option<&Hash>) { - if let Some(relay_parent) = relay_parent { - self.local_collations.remove(relay_parent); - } - - let now = Instant::now(); - self.local_collations.retain(|_, v| v.live_since + LIVE_FOR > now); - } - - /// Add a collation. Returns an iterator of session keys to send to and lazy copies of the collation. - pub fn add_collation<'a>( - &'a mut self, - relay_parent: Hash, - targets: HashSet, - collation: C - ) -> impl Iterator + 'a { - self.local_collations.insert(relay_parent, LocalCollation { - targets, - collation, - live_since: Instant::now(), - }); - - let local = self.local_collations.get(&relay_parent) - .expect("just inserted to this key; qed"); - - let borrowed_collation = &local.collation; - - // If we are conected to multiple validators, - // make sure we always send the collation to one of the validators - // we are registered as backup. This ensures that one collator that - // is primary at multiple validators, desn't block the Parachain from progressing. - let mut rng = rand::thread_rng(); - let diff = local.targets.difference(&self.primary_for).collect::>(); - - local.targets - .intersection(&self.primary_for) - .chain(diff.choose(&mut rng).map(|r| r.clone())) - .map(move |k| (k.clone(), borrowed_collation.clone())) - } - - fn collations_targeting(&self, key: &ValidatorId) -> Vec<(Hash, C)> { - self.local_collations.iter() - .filter(|&(_, ref v)| v.targets.contains(key)) - .map(|(h, v)| (*h, v.collation.clone())) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::crypto::UncheckedInto; - use polkadot_primitives::v0::ValidatorId; - - #[test] - fn add_validator_with_ready_collation() { - let key: ValidatorId = [1; 32].unchecked_into(); - let relay_parent = [2; 32].into(); - let targets = { - let mut set = HashSet::new(); - set.insert(key.clone()); - set - }; - - let mut tracker = LocalCollations::new(); - assert!(tracker.add_collation(relay_parent, targets, 5).next().is_some()); - assert_eq!(tracker.note_validator_role(key, Role::Primary), vec![(relay_parent, 5)]); - } - - #[test] - fn rename_with_ready() { - let orig_key: ValidatorId = [1; 32].unchecked_into(); - let new_key: ValidatorId = [2; 32].unchecked_into(); - let relay_parent = [255; 32].into(); - let targets = { - let mut set = HashSet::new(); - set.insert(new_key.clone()); - set - }; - - let mut tracker: LocalCollations = LocalCollations::new(); - assert!(tracker.add_collation(relay_parent, targets, 5).next().is_some()); - assert!(tracker.note_validator_role(orig_key.clone(), Role::Primary).is_empty()); - assert_eq!(tracker.fresh_key(&orig_key, &new_key), vec![(relay_parent, 5u8)]); - } - - #[test] - fn collecting_garbage() { - let relay_parent_a = [255; 32].into(); - let relay_parent_b = [222; 32].into(); - - let mut tracker: LocalCollations = LocalCollations::new(); - assert!(tracker.add_collation(relay_parent_a, HashSet::new(), 5).next().is_none()); - assert!(tracker.add_collation(relay_parent_b, HashSet::new(), 69).next().is_none()); - - let live_since = Instant::now() - LIVE_FOR - Duration::from_secs(10); - tracker.local_collations.get_mut(&relay_parent_b).unwrap().live_since = live_since; - - tracker.collect_garbage(Some(&relay_parent_a)); - - // first one pruned because of relay parent, other because of time. - assert!(tracker.local_collations.is_empty()); - } - - #[test] - fn add_collation_with_connected_target() { - let key: ValidatorId = [1; 32].unchecked_into(); - let relay_parent = [2; 32].into(); - let targets = { - let mut set = HashSet::new(); - set.insert(key.clone()); - set - }; - - let mut tracker = LocalCollations::new(); - assert!(tracker.note_validator_role(key.clone(), Role::Primary).is_empty()); - assert_eq!(tracker.add_collation(relay_parent, targets, 5).next(), Some((key, 5))); - - } -} diff --git a/network/src/legacy/mod.rs b/network/src/legacy/mod.rs deleted file mode 100644 index 42698657c053..000000000000 --- a/network/src/legacy/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Polkadot-specific network implementation. -//! -//! This manages routing for parachain statements, parachain block and outgoing message -//! data fetching, communication between collators and validators, and more. - -pub mod collator_pool; -pub mod local_collations; -pub mod gossip; - -use codec::Decode; -use futures::prelude::*; -use polkadot_primitives::v0::Hash; -use sc_network::PeerId; -use sc_network_gossip::TopicNotification; -use log::debug; - -use std::pin::Pin; -use std::task::{Context as PollContext, Poll}; - -use self::gossip::GossipMessage; - -/// Basic gossip functionality that a network has to fulfill. -pub trait GossipService { - /// Get a stream of gossip messages for a given hash. - fn gossip_messages_for(&self, topic: Hash) -> GossipMessageStream; - - /// Gossip a message on given topic. - fn gossip_message(&self, topic: Hash, message: GossipMessage); - - /// Send a message to a specific peer we're connected to. - fn send_message(&self, who: PeerId, message: GossipMessage); -} - -/// A stream of gossip messages and an optional sender for a topic. -pub struct GossipMessageStream { - topic_stream: Pin + Send>>, -} - -impl GossipMessageStream { - /// Create a new instance with the given topic stream. - pub fn new(topic_stream: Pin + Send>>) -> Self { - Self { - topic_stream, - } - } -} - -impl Stream for GossipMessageStream { - type Item = (GossipMessage, Option); - - fn poll_next(self: Pin<&mut Self>, cx: &mut PollContext) -> Poll> { - let this = Pin::into_inner(self); - - loop { - let msg = match Pin::new(&mut this.topic_stream).poll_next(cx) { - Poll::Ready(Some(msg)) => msg, - Poll::Ready(None) => return Poll::Ready(None), - Poll::Pending => return Poll::Pending, - }; - - debug!(target: "validation", "Processing statement for live validation leaf-work"); - if let Ok(gmsg) = GossipMessage::decode(&mut &msg.message[..]) { - return Poll::Ready(Some((gmsg, msg.sender))) - } - } - } -} diff --git a/network/src/lib.rs b/network/src/lib.rs deleted file mode 100644 index eaed7b34d2cb..000000000000 --- a/network/src/lib.rs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2020 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 . - -//! High-level network protocols for Polkadot. -//! -//! This manages routing for parachain statements, parachain block and outgoing message -//! data fetching, communication between collators and validators, and more. - -#![recursion_limit="256"] - -use polkadot_primitives::v0::{Block, Hash, BlakeTwo256, HashT}; - -pub mod legacy; -pub mod protocol; - -/// Specialization of the network service for the polkadot block type. -pub type PolkadotNetworkService = sc_network::NetworkService; - -mod cost { - use sc_network::ReputationChange as Rep; - pub(super) const UNEXPECTED_MESSAGE: Rep = Rep::new(-200, "Polkadot: Unexpected message"); - pub(super) const INVALID_FORMAT: Rep = Rep::new(-200, "Polkadot: Bad message"); - - pub(super) const UNKNOWN_PEER: Rep = Rep::new(-50, "Polkadot: Unknown peer"); - pub(super) const BAD_COLLATION: Rep = Rep::new(-1000, "Polkadot: Bad collation"); -} - -mod benefit { - use sc_network::ReputationChange as Rep; - pub(super) const VALID_FORMAT: Rep = Rep::new(20, "Polkadot: Valid message format"); - pub(super) const GOOD_COLLATION: Rep = Rep::new(100, "Polkadot: Good collation"); -} - -/// Compute gossip topic for the erasure chunk messages given the hash of the -/// candidate they correspond to. -fn erasure_coding_topic(candidate_hash: &Hash) -> Hash { - let mut v = candidate_hash.as_ref().to_vec(); - v.extend(b"erasure_chunks"); - - BlakeTwo256::hash(&v[..]) -} diff --git a/network/src/protocol/mod.rs b/network/src/protocol/mod.rs deleted file mode 100644 index 2626a5a3735f..000000000000 --- a/network/src/protocol/mod.rs +++ /dev/null @@ -1,1561 +0,0 @@ -// Copyright 2020 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. - -//! Polkadot-specific base networking protocol. -//! -//! This is implemented using the `sc-network` APIs for futures-based -//! notifications protocols. In some cases, we emulate request/response on top -//! of the notifications machinery, which is slightly less efficient but not -//! meaningfully so. -//! -//! We handle events from `sc-network` in a thin wrapper that forwards to a -//! background worker, which also handles commands from other parts of the node. - -use arrayvec::ArrayVec; -use codec::{Decode, Encode}; -use futures::channel::{mpsc, oneshot}; -use futures::future::Either; -use futures::prelude::*; -use futures::task::{Context, Poll}; -use futures::stream::{FuturesUnordered, StreamFuture}; -use log::{debug, trace}; - -use polkadot_primitives::v0::{ - Hash, Block, - PoVBlock, ValidatorId, ValidatorIndex, Collation, AbridgedCandidateReceipt, - ErasureChunk, ParachainHost, Id as ParaId, CollatorId, -}; -use polkadot_validation::{ - SharedTable, TableRouter, Network as ParachainNetwork, Validated, GenericStatement, Collators, - SignedStatement, -}; -use sc_network::{ObservedRole, Event, PeerId}; -use sp_api::ProvideRuntimeApi; -use sp_runtime::ConsensusEngineId; -use sp_core::traits::SpawnNamed; - -use std::collections::{hash_map::{Entry, HashMap}, HashSet}; -use std::pin::Pin; -use std::sync::Arc; -use std::time::Duration; - -use super::{cost, benefit, PolkadotNetworkService}; -use crate::legacy::collator_pool::Role as CollatorRole; -use crate::legacy::gossip::{GossipMessage, ErasureChunkMessage, RegisteredMessageValidator}; - -/// The current protocol version. -pub const VERSION: u32 = 1; -/// The minimum supported protocol version. -pub const MIN_SUPPORTED_VERSION: u32 = 1; - -/// The engine ID of the polkadot network protocol. -pub const POLKADOT_ENGINE_ID: ConsensusEngineId = *b"dot2"; -/// The protocol name. -pub const POLKADOT_PROTOCOL_NAME: &[u8] = b"/polkadot/1"; - -pub use crate::legacy::gossip::ChainContext; - -#[cfg(test)] -mod tests; - -// Messages from the service API or network adapter. -enum ServiceToWorkerMsg { - // basic peer messages. - PeerConnected(PeerId, ObservedRole), - PeerMessage(PeerId, Vec), - PeerDisconnected(PeerId), - - // service messages. - BuildConsensusNetworking(mpsc::Receiver, Arc, Vec), - SubmitValidatedCollation( - AbridgedCandidateReceipt, - PoVBlock, - (ValidatorIndex, Vec), - ), - FetchPoVBlock( - AbridgedCandidateReceipt, - oneshot::Sender, - ), - FetchErasureChunk( - Hash, // candidate-hash. - u32, // validator index. - oneshot::Sender, - ), - DistributeErasureChunk( - Hash, // candidate-hash, - ErasureChunk, - ), - AwaitCollation( - Hash, // relay-parent, - ParaId, - oneshot::Sender, - ), - NoteBadCollator( - CollatorId, - ), - RegisterAvailabilityStore( - av_store::Store, - ), - OurCollation( - HashSet, - Collation, - ), - ListenCheckedStatements( - Hash, // relay-parent, - oneshot::Sender + Send>>>, - ), - - /// Used in tests to ensure that all other messages sent from the same - /// thread have been flushed. Also executes arbitrary logic with the protocl - /// handler. - #[cfg(test)] - Synchronize(Box), -} - -/// Messages from a background task to the main worker task. -enum BackgroundToWorkerMsg { - // Spawn a given future. - // - // The name is used for the future task. - Spawn(&'static str, future::BoxFuture<'static, ()>), -} - -/// Operations that a handle to an underlying network service should provide. -pub trait NetworkServiceOps: Send + Sync { - /// Report the peer as having a particular positive or negative value. - fn report_peer(&self, peer: PeerId, value: sc_network::ReputationChange); - - /// Write a notification to a given peer. - fn write_notification( - &self, - peer: PeerId, - engine_id: ConsensusEngineId, - notification: Vec, - ); -} - -impl NetworkServiceOps for PolkadotNetworkService { - fn report_peer(&self, peer: PeerId, value: sc_network::ReputationChange) { - PolkadotNetworkService::report_peer(self, peer, value); - } - - fn write_notification( - &self, - peer: PeerId, - engine_id: ConsensusEngineId, - notification: Vec, - ) { - PolkadotNetworkService::write_notification(self, peer, engine_id, notification); - } -} - -/// Operations that a handle to a gossip network should provide. -trait GossipOps: Clone + Send + crate::legacy::GossipService + 'static { - fn new_local_leaf( - &self, - validation_data: crate::legacy::gossip::MessageValidationData, - ) -> crate::legacy::gossip::NewLeafActions; - - /// Register an availability store in the gossip service to evaluate incoming - /// messages with. - fn register_availability_store( - &self, - store: av_store::Store, - ); -} - -impl GossipOps for RegisteredMessageValidator { - fn new_local_leaf( - &self, - validation_data: crate::legacy::gossip::MessageValidationData, - ) -> crate::legacy::gossip::NewLeafActions { - RegisteredMessageValidator::new_local_leaf( - self, - validation_data, - ) - } - - fn register_availability_store( - &self, - store: av_store::Store, - ) { - RegisteredMessageValidator::register_availability_store(self, store); - } -} - -/// An async handle to the network service. -pub struct Service { - sender: mpsc::Sender, - network_service: Arc, -} - -impl Clone for Service { - fn clone(&self) -> Self { - Self { - sender: self.sender.clone(), - network_service: self.network_service.clone(), - } - } -} - -/// Registers the protocol. -/// -/// You are very strongly encouraged to call this method very early on. Any connection open -/// will retain the protocols that were registered then, and not any new one. -pub fn start( - service: Arc, - config: Config, - chain_context: C, - api: Arc, - executor: SP, -) -> Result, futures::task::SpawnError> where - C: ChainContext + 'static, - Api: ProvideRuntimeApi + Send + Sync + 'static, - Api::Api: ParachainHost, - SP: SpawnNamed + Clone + Send + Unpin + 'static, -{ - const SERVICE_TO_WORKER_BUF: usize = 256; - - let mut event_stream = service.event_stream("polkadot-network"); - service.register_notifications_protocol(POLKADOT_ENGINE_ID, POLKADOT_PROTOCOL_NAME); - let (mut worker_sender, worker_receiver) = mpsc::channel(SERVICE_TO_WORKER_BUF); - - let gossip_validator = crate::legacy::gossip::register_validator( - service.clone(), - chain_context, - &executor, - ); - executor.spawn( - "polkadot-network-worker", - worker_loop( - config, - service.clone(), - gossip_validator, - api, - worker_receiver, - executor.clone(), - ).boxed(), - ); - - let polkadot_service = Service { - sender: worker_sender.clone(), - network_service: service.clone(), - }; - - executor.spawn( - "polkadot-network-notifications", - async move { - while let Some(event) = event_stream.next().await { - let res = match event { - Event::Dht(_) => continue, - Event::NotificationStreamOpened { - remote, - engine_id, - role, - } => { - if engine_id != POLKADOT_ENGINE_ID { continue } - - worker_sender.send(ServiceToWorkerMsg::PeerConnected(remote, role)).await - }, - Event::NotificationStreamClosed { - remote, - engine_id, - } => { - if engine_id != POLKADOT_ENGINE_ID { continue } - - worker_sender.send(ServiceToWorkerMsg::PeerDisconnected(remote)).await - }, - Event::NotificationsReceived { - remote, - messages, - } => { - let our_notifications = messages.into_iter() - .filter_map(|(engine, message)| if engine == POLKADOT_ENGINE_ID { - Some(message) - } else { - None - }) - .collect(); - - worker_sender.send( - ServiceToWorkerMsg::PeerMessage(remote, our_notifications) - ).await - } - }; - - if let Err(e) = res { - // full is impossible here, as we've `await`ed the value being sent. - if e.is_disconnected() { - break - } - } - } - }.boxed(), - ); - - Ok(polkadot_service) -} - -/// The Polkadot protocol status message. -#[derive(Debug, Encode, Decode, PartialEq)] -pub struct Status { - version: u32, // protocol version. - collating_for: Option<(CollatorId, ParaId)>, -} - -/// Polkadot-specific messages from peer to peer. -#[derive(Debug, Encode, Decode, PartialEq)] -pub enum Message { - /// Exchange status with a peer. This should be the first message sent. - #[codec(index = "0")] - Status(Status), - /// Inform a peer of their role as a collator. May only be sent after - /// validator ID. - #[codec(index = "1")] - CollatorRole(CollatorRole), - /// Send a collation. - #[codec(index = "2")] - Collation(Hash, Collation), - /// Inform a peer of a new validator public key. - #[codec(index = "3")] - ValidatorId(ValidatorId), -} - -// ensures collator-protocol messages are sent in correct order. -// session key must be sent before collator role. -enum CollatorState { - Fresh, - RolePending(CollatorRole), - Primed(Option), -} - -impl CollatorState { - fn send_key(&mut self, key: ValidatorId, mut f: F) { - f(Message::ValidatorId(key)); - match self { - CollatorState::RolePending(role) => { - f(Message::CollatorRole(*role)); - *self = CollatorState::Primed(Some(*role)); - }, - CollatorState::Fresh => { - *self = CollatorState::Primed(None); - }, - CollatorState::Primed(_) => {}, - } - } - - fn set_role(&mut self, role: CollatorRole, mut f: F) { - if let CollatorState::Primed(ref mut r) = *self { - f(Message::CollatorRole(role)); - *r = Some(role); - } else { - *self = CollatorState::RolePending(role); - } - } -} - -enum ProtocolState { - Fresh, - Ready(Status, CollatorState), -} - -struct PeerData { - claimed_validator: bool, - protocol_state: ProtocolState, - session_keys: RecentValidatorIds, -} - -impl PeerData { - fn ready_and_collating_for(&self) -> Option<(CollatorId, ParaId)> { - match self.protocol_state { - ProtocolState::Ready(ref status, _) => status.collating_for.clone(), - _ => None, - } - } - - fn collator_state_mut(&mut self) -> Option<&mut CollatorState> { - match self.protocol_state { - ProtocolState::Ready(_, ref mut c_state) => Some(c_state), - _ => None, - } - } - - fn should_send_key(&self) -> bool { - self.claimed_validator || self.ready_and_collating_for().is_some() - } -} - -struct ConsensusNetworkingInstance { - statement_table: Arc, - relay_parent: Hash, - attestation_topic: Hash, - _drop_signal: exit_future::Signal, -} - -/// Protocol configuration. -#[derive(Default)] -pub struct Config { - /// Which collator-id to use when collating, and on which parachain. - /// `None` if not collating. - pub collating_for: Option<(CollatorId, ParaId)>, -} - -// 3 is chosen because sessions change infrequently and usually -// only the last 2 (current session and "last" session) are relevant. -// the extra is an error boundary. -const RECENT_SESSIONS: usize = 3; - -/// Result when inserting recent session key. -#[derive(PartialEq, Eq)] -pub(crate) enum InsertedRecentKey { - /// Key was already known. - AlreadyKnown, - /// Key was new and pushed out optional old item. - New(Option), -} - -/// Wrapper for managing recent session keys. -#[derive(Default)] -struct RecentValidatorIds { - inner: ArrayVec<[ValidatorId; RECENT_SESSIONS]>, -} - -impl RecentValidatorIds { - /// Insert a new session key. This returns one to be pushed out if the - /// set is full. - fn insert(&mut self, key: ValidatorId) -> InsertedRecentKey { - if self.inner.contains(&key) { return InsertedRecentKey::AlreadyKnown } - - let old = if self.inner.len() == RECENT_SESSIONS { - Some(self.inner.remove(0)) - } else { - None - }; - - self.inner.push(key); - InsertedRecentKey::New(old) - } - - /// As a slice. Most recent is last. - fn as_slice(&self) -> &[ValidatorId] { - &*self.inner - } - - /// Returns the last inserted session key. - fn latest(&self) -> Option<&ValidatorId> { - self.inner.last() - } -} - -struct ProtocolHandler { - service: Arc, - peers: HashMap, - // reverse mapping from validator-ID to PeerID. Multiple peers can represent - // the same validator because of sentry nodes. - connected_validators: HashMap>, - consensus_instances: HashMap, - collators: crate::legacy::collator_pool::CollatorPool, - local_collations: crate::legacy::local_collations::LocalCollations, - config: Config, - local_keys: RecentValidatorIds, -} - -impl ProtocolHandler { - fn new( - service: Arc, - config: Config, - ) -> Self { - ProtocolHandler { - service, - peers: HashMap::new(), - connected_validators: HashMap::new(), - consensus_instances: HashMap::new(), - collators: Default::default(), - local_collations: Default::default(), - local_keys: Default::default(), - config, - } - } - - fn on_connect(&mut self, peer: PeerId, role: ObservedRole) { - let claimed_validator = matches!( - role, - ObservedRole::OurSentry | ObservedRole::OurGuardedAuthority | ObservedRole::Authority - ); - - self.peers.insert(peer.clone(), PeerData { - claimed_validator, - protocol_state: ProtocolState::Fresh, - session_keys: Default::default(), - }); - - let status = Message::Status(Status { - version: VERSION, - collating_for: self.config.collating_for.clone(), - }).encode(); - - self.service.write_notification(peer, POLKADOT_ENGINE_ID, status); - } - - fn on_disconnect(&mut self, peer: PeerId) { - let mut new_primary = None; - if let Some(data) = self.peers.remove(&peer) { - // replace collator. - if let Some((collator_id, _)) = data.ready_and_collating_for() { - if self.collators.collator_id_to_peer_id(&collator_id) == Some(&peer) { - new_primary = self.collators.on_disconnect(collator_id); - } - } - - // clean up stated validator IDs. - for validator_id in data.session_keys.as_slice().iter().cloned() { - self.validator_representative_removed(validator_id, &peer); - } - } - - let service = &self.service; - let peers = &mut self.peers; - if let Some(new_primary) = new_primary { - let new_primary_peer_id = match self.collators.collator_id_to_peer_id(&new_primary) { - None => return, - Some(p) => p.clone(), - }; - if let Some(c_state) = peers.get_mut(&new_primary_peer_id) - .and_then(|p| p.collator_state_mut()) - { - c_state.set_role( - CollatorRole::Primary, - |msg| service.write_notification( - new_primary_peer_id.clone(), - POLKADOT_ENGINE_ID, - msg.encode(), - ), - ); - } - } - } - - fn on_raw_messages(&mut self, remote: PeerId, messages: Vec) { - for raw_message in messages { - match Message::decode(&mut raw_message.as_ref()) { - Ok(message) => { - self.service.report_peer(remote.clone(), benefit::VALID_FORMAT); - match message { - Message::Status(status) => { - self.on_status(remote.clone(), status); - } - Message::CollatorRole(role) => { - self.on_collator_role(remote.clone(), role) - } - Message::Collation(relay_parent, collation) => { - self.on_remote_collation(remote.clone(), relay_parent, collation); - } - Message::ValidatorId(session_key) => { - self.on_validator_id(remote.clone(), session_key) - } - } - }, - Err(_) => self.service.report_peer(remote.clone(), cost::INVALID_FORMAT), - } - } - } - - fn on_status(&mut self, remote: PeerId, status: Status) { - let peer = match self.peers.get_mut(&remote) { - None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return } - Some(p) => p, - }; - - match peer.protocol_state { - ProtocolState::Fresh => { - peer.protocol_state = ProtocolState::Ready(status, CollatorState::Fresh); - if let Some((collator_id, para_id)) = peer.ready_and_collating_for() { - let collator_attached = self.collators - .collator_id_to_peer_id(&collator_id) - .map_or(false, |id| id != &remote); - - // we only care about the first connection from this collator. - if !collator_attached { - let role = self.collators - .on_new_collator(collator_id, para_id, remote.clone()); - let service = &self.service; - let send_key = peer.should_send_key(); - - if let Some(c_state) = peer.collator_state_mut() { - if send_key { - if let Some(key) = self.local_keys.latest() { - c_state.send_key(key.clone(), |msg| service.write_notification( - remote.clone(), - POLKADOT_ENGINE_ID, - msg.encode(), - )); - } - } - - c_state.set_role(role, |msg| service.write_notification( - remote.clone(), - POLKADOT_ENGINE_ID, - msg.encode(), - )); - } - } - } - } - ProtocolState::Ready(_, _) => { - self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE); - } - } - } - - fn on_remote_collation(&mut self, remote: PeerId, relay_parent: Hash, collation: Collation) { - let peer = match self.peers.get_mut(&remote) { - None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return } - Some(p) => p, - }; - - let (collator_id, para_id) = match peer.ready_and_collating_for() { - None => { - self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE); - return - } - Some(x) => x, - }; - - let collation_para = collation.info.parachain_index; - let collated_acc = collation.info.collator.clone(); - - let structurally_valid = para_id == collation_para && collator_id == collated_acc; - if structurally_valid && collation.info.check_signature().is_ok() { - debug!(target: "p_net", "Received collation for parachain {:?} from peer {}", - para_id, remote); - - if self.collators.collator_id_to_peer_id(&collator_id) == Some(&remote) { - self.collators.on_collation(collator_id, relay_parent, collation); - self.service.report_peer(remote, benefit::GOOD_COLLATION); - } - } else { - self.service.report_peer(remote, cost::INVALID_FORMAT); - } - } - - fn on_collator_role(&mut self, remote: PeerId, role: CollatorRole) { - let collations_to_send; - - { - let peer = match self.peers.get_mut(&remote) { - None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return } - Some(p) => p, - }; - - match peer.protocol_state { - ProtocolState::Fresh => { - self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE); - return; - } - ProtocolState::Ready(_, _) => { - let last_key = match peer.session_keys.as_slice().last() { - None => { - self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE); - return; - } - Some(k) => k, - }; - - collations_to_send = self.local_collations - .note_validator_role(last_key.clone(), role); - } - } - } - - send_peer_collations(&*self.service, remote, collations_to_send); - } - - fn on_validator_id(&mut self, remote: PeerId, key: ValidatorId) { - let mut collations_to_send = Vec::new(); - let mut invalidated_key = None; - - { - let peer = match self.peers.get_mut(&remote) { - None => { self.service.report_peer(remote, cost::UNKNOWN_PEER); return } - Some(p) => p, - }; - - match peer.protocol_state { - ProtocolState::Fresh => { - self.service.report_peer(remote, cost::UNEXPECTED_MESSAGE); - return - } - ProtocolState::Ready(_, _) => { - if let InsertedRecentKey::New(Some(last)) = peer.session_keys.insert(key.clone()) { - collations_to_send = self.local_collations.fresh_key(&last, &key); - invalidated_key = Some(last); - } - } - } - } - - if let Some(invalidated) = invalidated_key { - self.validator_representative_removed(invalidated, &remote); - } - self.connected_validators.entry(key).or_insert_with(HashSet::new).insert(remote.clone()); - - send_peer_collations(&*self.service, remote, collations_to_send); - } - - // call when the given peer no longer represents the given validator key. - // - // this can occur when the peer advertises a new key, invalidating an old one, - // or when the peer disconnects. - fn validator_representative_removed(&mut self, validator_id: ValidatorId, peer_id: &PeerId) { - if let Entry::Occupied(mut entry) = self.connected_validators.entry(validator_id) { - entry.get_mut().remove(peer_id); - if entry.get().is_empty() { - let _ = entry.remove_entry(); - } - } - } - - fn await_collation( - &mut self, - relay_parent: Hash, - para_id: ParaId, - sender: oneshot::Sender, - ) { - self.collators.await_collation(relay_parent, para_id, sender); - } - - fn collect_garbage(&mut self) { - self.collators.collect_garbage(None); - self.local_collations.collect_garbage(None); - } - - fn note_bad_collator(&mut self, who: CollatorId) { - if let Some(peer) = self.collators.collator_id_to_peer_id(&who) { - self.service.report_peer(peer.clone(), cost::BAD_COLLATION); - } - } - - // distribute a new session key to any relevant peers. - fn distribute_new_session_key(&mut self, key: ValidatorId) { - let service = &self.service; - - for (peer_id, peer) in self.peers.iter_mut() { - if !peer.should_send_key() { continue } - - if let Some(c_state) = peer.collator_state_mut() { - c_state.send_key(key.clone(), |msg| service.write_notification( - peer_id.clone(), - POLKADOT_ENGINE_ID, - msg.encode(), - )); - } - } - } - - // distribute our (as a collator node) collation to peers. - fn distribute_our_collation(&mut self, targets: HashSet, collation: Collation) { - let relay_parent = collation.info.relay_parent; - let distribution = self.local_collations.add_collation(relay_parent, targets, collation); - - for (validator, collation) in distribution { - let validator_representatives = self.connected_validators.get(&validator) - .into_iter().flat_map(|reps| reps); - - for remote in validator_representatives { - send_peer_collations( - &*self.service, - remote.clone(), - std::iter::once((relay_parent, collation.clone())), - ); - } - } - } - - fn drop_consensus_networking(&mut self, relay_parent: &Hash) { - // this triggers an abort of the background task. - self.consensus_instances.remove(relay_parent); - } -} - -fn send_peer_collations( - service: &dyn NetworkServiceOps, - remote: PeerId, - collations: impl IntoIterator, -) { - for (relay_parent, collation) in collations { - service.write_notification( - remote.clone(), - POLKADOT_ENGINE_ID, - Message::Collation(relay_parent, collation).encode(), - ); - } -} - -/// Receives messages associated to a certain consensus networking instance. -struct ConsensusNetworkingReceiver { - receiver: mpsc::Receiver, - /// The relay parent of this consensus network. - relay_parent: Hash, -} - -impl Stream for ConsensusNetworkingReceiver { - type Item = ServiceToWorkerMsg; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.receiver).poll_next(cx) - } -} - -struct Worker { - protocol_handler: ProtocolHandler, - api: Arc, - executor: Sp, - gossip_handle: Gossip, - background_to_main_sender: mpsc::Sender, - background_receiver: mpsc::Receiver, - service_receiver: mpsc::Receiver, - consensus_networking_receivers: FuturesUnordered>, -} - -impl Worker where - Api: ProvideRuntimeApi + Send + Sync + 'static, - Api::Api: ParachainHost, - Sp: SpawnNamed + Clone + Unpin + 'static, - Gossip: GossipOps, -{ - // spawns a background task to spawn consensus networking. - fn build_consensus_networking( - &mut self, - receiver: mpsc::Receiver, - table: Arc, - authorities: Vec, - ) { - // glue: let gossip know about our new local leaf. - let (signal, exit) = exit_future::signal(); - - let key = table.session_key(); - if let Some(key) = key { - if let InsertedRecentKey::New(_) = self.protocol_handler.local_keys.insert(key.clone()) { - self.protocol_handler.distribute_new_session_key(key); - } - } - - let signing_context = table.signing_context().clone(); - let relay_parent = signing_context.parent_hash.clone(); - let new_leaf_actions = self.gossip_handle.new_local_leaf( - crate::legacy::gossip::MessageValidationData { authorities, signing_context }, - ); - - new_leaf_actions.perform(&self.gossip_handle); - - self.protocol_handler.consensus_instances.insert( - relay_parent.clone(), - ConsensusNetworkingInstance { - statement_table: table.clone(), - relay_parent: relay_parent.clone(), - attestation_topic: crate::legacy::gossip::attestation_topic(relay_parent.clone()), - _drop_signal: signal, - }, - ); - - let relay_parent = table.signing_context().parent_hash; - self.consensus_networking_receivers.push(ConsensusNetworkingReceiver { receiver, relay_parent }.into_future()); - - // glue the incoming messages, shared table, and validation - // work together. - self.executor.spawn( - "polkadot-statement-import-loop", - statement_import_loop( - relay_parent, - table, - self.api.clone(), - self.gossip_handle.clone(), - self.background_to_main_sender.clone(), - exit, - self.executor.clone(), - ).boxed(), - ); - } - - fn handle_service_message(&mut self, message: ServiceToWorkerMsg) { - match message { - ServiceToWorkerMsg::PeerConnected(remote, role) => { - self.protocol_handler.on_connect(remote, role); - } - ServiceToWorkerMsg::PeerDisconnected(remote) => { - self.protocol_handler.on_disconnect(remote); - } - ServiceToWorkerMsg::PeerMessage(remote, messages) => { - self.protocol_handler.on_raw_messages(remote, messages) - } - ServiceToWorkerMsg::BuildConsensusNetworking(receiver, table, authorities) => { - self.build_consensus_networking(receiver, table, authorities); - } - ServiceToWorkerMsg::SubmitValidatedCollation(receipt, pov_block, chunks) => { - let relay_parent = receipt.relay_parent; - let instance = match self.protocol_handler.consensus_instances.get(&relay_parent) { - None => return, - Some(instance) => instance, - }; - - distribute_validated_collation( - instance, - receipt, - pov_block, - chunks, - &self.gossip_handle, - ); - } - 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); - - self.executor.spawn( - "polkadot-fetch-pov-block", - async move { - let res = future::select(get_msg, sender.cancellation()).await; - if let Either::Left((pov_block, _)) = res { - let _ = sender.send(pov_block); - } - }.boxed(), - ); - } - ServiceToWorkerMsg::FetchErasureChunk(candidate_hash, validator_index, mut sender) => { - let topic = crate::erasure_coding_topic(&candidate_hash); - - // for every erasure-root, relay-parent pair, there should only be one - // valid chunk with the given index. - // - // so we only care about the first item of the filtered stream. - let get_msg = self.gossip_handle.gossip_messages_for(topic) - .filter_map(move |(msg, _)| { - future::ready(match msg { - GossipMessage::ErasureChunk(chunk) => - if chunk.chunk.index == validator_index { - Some(chunk.chunk) - } else { - None - }, - _ => None, - }) - }) - .into_future() - .map(|(item, _)| item.expect( - "gossip message streams do not conclude early; qed" - )); - - self.executor.spawn( - "polkadot-fetch-erasure-chunk", - async move { - let res = future::select(get_msg, sender.cancellation()).await; - if let Either::Left((chunk, _)) = res { - let _ = sender.send(chunk); - } - }.boxed(), - ); - } - ServiceToWorkerMsg::DistributeErasureChunk(candidate_hash, erasure_chunk) => { - let topic = crate::erasure_coding_topic(&candidate_hash); - self.gossip_handle.gossip_message( - topic, - GossipMessage::ErasureChunk(ErasureChunkMessage { - chunk: erasure_chunk, - candidate_hash, - }) - ); - } - ServiceToWorkerMsg::AwaitCollation(relay_parent, para_id, sender) => { - debug!( - target: "p_net", "Attempting to get collation for parachain {:?} on relay parent {:?}", - para_id, - relay_parent, - ); - self.protocol_handler.await_collation(relay_parent, para_id, sender) - } - ServiceToWorkerMsg::NoteBadCollator(collator) => { - self.protocol_handler.note_bad_collator(collator); - } - ServiceToWorkerMsg::RegisterAvailabilityStore(store) => { - self.gossip_handle.register_availability_store(store); - } - ServiceToWorkerMsg::OurCollation(targets, collation) => { - self.protocol_handler.distribute_our_collation(targets, collation); - } - ServiceToWorkerMsg::ListenCheckedStatements(relay_parent, sender) => { - let topic = crate::legacy::gossip::attestation_topic(relay_parent); - let checked_messages = self.gossip_handle.gossip_messages_for(topic) - .filter_map(|msg| match msg.0 { - GossipMessage::Statement(s) => future::ready(Some(s.signed_statement)), - _ => future::ready(None), - }) - .boxed(); - - let _ = sender.send(checked_messages); - } - #[cfg(test)] - ServiceToWorkerMsg::Synchronize(callback) => { - (callback)(&mut self.protocol_handler) - } - } - } - - fn handle_background_message(&mut self, message: BackgroundToWorkerMsg) { - match message { - BackgroundToWorkerMsg::Spawn(name, task) => { - let _ = self.executor.spawn(name, task); - } - } - } - - async fn main_loop(&mut self) { - const COLLECT_GARBAGE_INTERVAL: Duration = Duration::from_secs(29); - - let mut collect_garbage = stream::unfold((), move |_| { - futures_timer::Delay::new(COLLECT_GARBAGE_INTERVAL).map(|_| Some(((), ()))) - }).map(drop); - - loop { - futures::select! { - _do_collect = collect_garbage.next() => { - self.protocol_handler.collect_garbage(); - } - service_msg = self.service_receiver.next() => match service_msg { - Some(msg) => self.handle_service_message(msg), - None => return, - }, - consensus_service_msg = self.consensus_networking_receivers.next() => match consensus_service_msg { - Some((Some(msg), receiver)) => { - self.handle_service_message(msg); - self.consensus_networking_receivers.push(receiver.into_future()); - }, - Some((None, receiver)) => { - self.protocol_handler.drop_consensus_networking(&receiver.relay_parent); - }, - None => {}, - }, - background_msg = self.background_receiver.next() => match background_msg { - Some(msg) => self.handle_background_message(msg), - None => return, - }, - } - } - } -} - -async fn worker_loop( - config: Config, - service: Arc, - gossip_handle: impl GossipOps, - api: Arc, - receiver: mpsc::Receiver, - executor: Sp, -) where - Api: ProvideRuntimeApi + Send + Sync + 'static, - Api::Api: ParachainHost, - Sp: SpawnNamed + Clone + Unpin + 'static, -{ - const BACKGROUND_TO_MAIN_BUF: usize = 16; - - let (background_tx, background_rx) = mpsc::channel(BACKGROUND_TO_MAIN_BUF); - let mut worker = Worker { - protocol_handler: ProtocolHandler::new(service, config), - api, - executor, - gossip_handle, - background_to_main_sender: background_tx, - background_receiver: background_rx, - service_receiver: receiver, - consensus_networking_receivers: Default::default(), - }; - - worker.main_loop().await -} - -// A unique trace for valid statements issued by a validator. -#[derive(Hash, PartialEq, Eq, Clone, Debug)] -pub(crate) enum StatementTrace { - Valid(ValidatorIndex, Hash), - Invalid(ValidatorIndex, Hash), -} - -/// Helper for deferring statements whose associated candidate is unknown. -struct DeferredStatements { - deferred: HashMap>, - known_traces: HashSet, -} - -impl DeferredStatements { - /// Create a new `DeferredStatements`. - fn new() -> Self { - DeferredStatements { - deferred: HashMap::new(), - known_traces: HashSet::new(), - } - } - - /// Push a new statement onto the deferred pile. `Candidate` statements - /// cannot be deferred and are ignored. - fn push(&mut self, statement: SignedStatement) { - let (hash, trace) = match statement.statement { - GenericStatement::Candidate(_) => return, - GenericStatement::Valid(hash) => (hash, StatementTrace::Valid(statement.sender.clone(), hash)), - GenericStatement::Invalid(hash) => (hash, StatementTrace::Invalid(statement.sender.clone(), hash)), - }; - - if self.known_traces.insert(trace) { - self.deferred.entry(hash).or_insert_with(Vec::new).push(statement); - } - } - - /// Take all deferred statements referencing the given candidate hash out. - fn take_deferred(&mut self, hash: &Hash) -> (Vec, Vec) { - match self.deferred.remove(hash) { - None => (Vec::new(), Vec::new()), - Some(deferred) => { - let mut traces = Vec::new(); - for statement in deferred.iter() { - let trace = match statement.statement { - GenericStatement::Candidate(_) => continue, - GenericStatement::Valid(hash) => StatementTrace::Valid(statement.sender.clone(), hash), - GenericStatement::Invalid(hash) => StatementTrace::Invalid(statement.sender.clone(), hash), - }; - - self.known_traces.remove(&trace); - traces.push(trace); - } - - (deferred, traces) - } - } - } -} - -// the internal loop of waiting for messages and spawning validation work -// as a result of those messages. this future exits when `exit` is ready. -async fn statement_import_loop( - relay_parent: Hash, - table: Arc, - api: Arc, - gossip_handle: impl GossipOps, - mut to_worker: mpsc::Sender, - mut exit: exit_future::Exit, - spawner: impl SpawnNamed + Clone + Unpin + 'static, -) where - Api: ProvideRuntimeApi + Send + Sync + 'static, - Api::Api: ParachainHost, -{ - let topic = crate::legacy::gossip::attestation_topic(relay_parent); - let mut checked_messages = gossip_handle.gossip_messages_for(topic) - .filter_map(|msg| match msg.0 { - GossipMessage::Statement(s) => future::ready(Some(s.signed_statement)), - _ => future::ready(None), - }); - - let mut deferred_statements = DeferredStatements::new(); - - loop { - let statement = match future::select(exit, checked_messages.next()).await { - Either::Left(_) | Either::Right((None, _)) => return, - Either::Right((Some(statement), e)) => { - exit = e; - statement - } - }; - - // defer any statements for which we haven't imported the candidate yet - let c_hash = { - let candidate_data = match statement.statement { - GenericStatement::Candidate(ref c) => Some(c.hash()), - GenericStatement::Valid(ref hash) - | GenericStatement::Invalid(ref hash) - => table.with_candidate(hash, |c| c.map(|_| *hash)), - }; - match candidate_data { - Some(x) => x, - None => { - deferred_statements.push(statement); - continue; - } - } - }; - - // import all statements pending on this candidate - let (mut statements, _traces) = if let GenericStatement::Candidate(_) = statement.statement { - deferred_statements.take_deferred(&c_hash) - } else { - (Vec::new(), Vec::new()) - }; - - // prepend the candidate statement. - debug!(target: "validation", "Importing statements about candidate {:?}", c_hash); - statements.insert(0, statement); - - let producers: Vec<_> = { - 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( - &fetch_pov, - statements.iter().cloned(), - ) - }; - - // dispatch future work as necessary. - for (producer, statement) in producers.into_iter().zip(statements) { - if let Some(_sender) = table.index_to_id(statement.sender) { - if let Some(producer) = producer { - trace!(target: "validation", "driving statement work to completion"); - - let table = table.clone(); - let gossip_handle = gossip_handle.clone(); - - let work = producer.prime(api.clone(), spawner.clone()).validate().map(move |res| { - let validated = match res { - Err(e) => { - debug!(target: "p_net", "Failed to act on statement: {}", e); - return - } - Ok(v) => v, - }; - - // propagate the statement. - let statement = crate::legacy::gossip::GossipStatement::new( - relay_parent, - match table.import_validated(validated) { - Some(s) => s, - None => return, - } - ); - - gossip_handle.gossip_message(topic, statement.into()); - }); - - let work = future::select(work.boxed(), exit.clone()).map(drop); - if let Err(_) = to_worker.send( - BackgroundToWorkerMsg::Spawn("polkadot-statement-import-loop-sub-task", work.boxed()) - ).await { - // can fail only if remote has hung up - worker is dead, - // we should die too. this is defensive, since the exit future - // would fire shortly anyway. - return - } - } - } - } - } -} - -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. -fn distribute_validated_collation( - instance: &ConsensusNetworkingInstance, - receipt: AbridgedCandidateReceipt, - pov_block: PoVBlock, - chunks: (ValidatorIndex, Vec), - gossip_handle: &impl GossipOps, -) { - // produce a signed statement. - let hash = receipt.hash(); - let validated = Validated::collated_local( - receipt, - pov_block.clone(), - ); - - // 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( - 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, - candidate_hash: hash, - }; - - gossip_handle.gossip_message( - crate::erasure_coding_topic(&hash), - message.into(), - ); - } -} - -/// Routing logic for a particular attestation session. -#[derive(Clone)] -pub struct Router { - inner: Arc, -} - -// note: do _not_ make this `Clone`: the drop implementation needs to _uniquely_ -// send the `DropConsensusNetworking` message. -struct RouterInner { - relay_parent: Hash, - sender: mpsc::Sender, -} - -impl Service { - /// Register an availablility-store that the network can query. - pub fn register_availability_store(&self, store: av_store::Store) { - let _ = self.sender.clone() - .try_send(ServiceToWorkerMsg::RegisterAvailabilityStore(store)); - } - - /// Submit a collation that we (as a collator) have prepared to validators. - /// - /// Provide a set of validator-IDs we should distribute to. - pub fn distribute_collation(&self, targets: HashSet, collation: Collation) { - let _ = self.sender.clone() - .try_send(ServiceToWorkerMsg::OurCollation(targets, collation)); - } - - /// Returns a stream that listens for checked statements on a particular - /// relay chain parent hash. - /// - /// Take care to drop the stream, as the sending side will not be cleaned - /// up until it is. - pub fn checked_statements(&self, relay_parent: Hash) - -> impl Stream + Send { - let (tx, rx) = oneshot::channel(); - let mut sender = self.sender.clone(); - - let receive_stream = async move { - sender.send( - ServiceToWorkerMsg::ListenCheckedStatements(relay_parent, tx) - ).map_err(future::Either::Left).await?; - - rx.map_err(future::Either::Right).await - }; - - receive_stream - .map(|res| match res { - Ok(s) => s.left_stream(), - Err(e) => { - log::warn!( - target: "p_net", - "Polkadot network worker appears to be down: {:?}", - e, - ); - stream::pending().right_stream() - } - }) - .flatten_stream() - } -} - -impl ParachainNetwork for Service { - type Error = mpsc::SendError; - type TableRouter = Router; - type BuildTableRouter = Pin> + Send>>; - - fn build_table_router( - &self, - table: Arc, - authorities: &[ValidatorId], - ) -> Self::BuildTableRouter { - let authorities = authorities.to_vec(); - let mut sender = self.sender.clone(); - let relay_parent = table.signing_context().parent_hash.clone(); - - Box::pin(async move { - let (router_sender, receiver) = mpsc::channel(0); - sender.send( - ServiceToWorkerMsg::BuildConsensusNetworking(receiver, table, authorities) - ).await?; - - Ok(Router { - inner: Arc::new(RouterInner { - relay_parent, - sender: router_sender, - }) - }) - }) - } -} - -impl Collators for Service { - type Error = future::Either; - type Collation = Pin> + Send>>; - - fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation { - let (tx, rx) = oneshot::channel(); - let mut sender = self.sender.clone(); - - Box::pin(async move { - sender.send( - ServiceToWorkerMsg::AwaitCollation(relay_parent, parachain, tx) - ).map_err(future::Either::Left).await?; - - rx.map_err(future::Either::Right).await - }) - } - - fn note_bad_collator(&self, collator: CollatorId) { - let _ = self.sender.clone().try_send(ServiceToWorkerMsg::NoteBadCollator(collator)); - } -} - -impl av_store::ErasureNetworking for Service { - type Error = future::Either; - - fn fetch_erasure_chunk(&self, candidate_hash: &Hash, index: u32) - -> Pin> + Send>> - { - let (tx, rx) = oneshot::channel(); - let mut sender = self.sender.clone(); - - let candidate_hash = *candidate_hash; - Box::pin(async move { - sender.send( - ServiceToWorkerMsg::FetchErasureChunk(candidate_hash, index, tx) - ).map_err(future::Either::Left).await?; - - rx.map_err(future::Either::Right).await - }) - } - - fn distribute_erasure_chunk( - &self, - candidate_hash: Hash, - chunk: ErasureChunk, - ) { - let _ = self.sender.clone().try_send( - ServiceToWorkerMsg::DistributeErasureChunk(candidate_hash, chunk) - ); - } -} - -impl sp_consensus::SyncOracle for Service where for<'r> &'r N: sp_consensus::SyncOracle { - fn is_major_syncing(&mut self) -> bool { - self.network_service.is_major_syncing() - } - - fn is_offline(&mut self) -> bool { - self.network_service.is_offline() - } -} - -/// Errors when interacting with the statement router. -#[derive(Debug, derive_more::Display, derive_more::From)] -pub enum RouterError { - #[display(fmt = "Encountered unexpected I/O error: {}", _0)] - Io(std::io::Error), - #[display(fmt = "Worker hung up while answering request.")] - Canceled(oneshot::Canceled), - #[display(fmt = "Could not reach worker with request: {}", _0)] - SendError(mpsc::SendError), - #[display(fmt = "Provided candidate receipt does not have expected relay parent {}", _0)] - IncorrectRelayParent(Hash), -} - -impl TableRouter for Router { - type Error = RouterError; - type SendLocalCollation = Pin> + Send>>; - type FetchValidationProof = Pin> + Send>>; - - fn local_collation( - &self, - receipt: AbridgedCandidateReceipt, - pov_block: PoVBlock, - chunks: (ValidatorIndex, &[ErasureChunk]), - ) -> Self::SendLocalCollation { - if receipt.relay_parent != self.inner.relay_parent { - return Box::pin( - future::ready(Err(RouterError::IncorrectRelayParent(self.inner.relay_parent))) - ); - } - - let message = ServiceToWorkerMsg::SubmitValidatedCollation( - receipt, - pov_block, - (chunks.0, chunks.1.to_vec()), - ); - let mut sender = self.inner.sender.clone(); - Box::pin(async move { - sender.send(message).map_err(Into::into).await - }) - } - - fn fetch_pov_block(&self, candidate: &AbridgedCandidateReceipt) -> Self::FetchValidationProof { - if candidate.relay_parent != self.inner.relay_parent { - return Box::pin( - future::ready(Err(RouterError::IncorrectRelayParent(self.inner.relay_parent))) - ); - } - - let (tx, rx) = oneshot::channel(); - let message = ServiceToWorkerMsg::FetchPoVBlock( - candidate.clone(), - tx, - ); - - let mut sender = self.inner.sender.clone(); - Box::pin(async move { - sender.send(message).await?; - rx.map_err(Into::into).await - }) - } -} diff --git a/network/src/protocol/tests.rs b/network/src/protocol/tests.rs deleted file mode 100644 index 3bc4537cfa74..000000000000 --- a/network/src/protocol/tests.rs +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright 2020 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. - -//! Tests for the protocol. - -use super::*; -use crate::legacy::gossip::GossipPoVBlock; -use parking_lot::Mutex; - -use polkadot_primitives::v0::{ - Block, - Id as ParaId, Chain, DutyRoster, ParachainHost, ValidatorId, - Retriable, CollatorId, AbridgedCandidateReceipt, - GlobalValidationData, LocalValidationData, SigningContext, - PoVBlock, BlockData, ValidationCode, -}; -use polkadot_validation::{SharedTable, TableRouter}; - -use av_store::Store as AvailabilityStore; -use sc_network_gossip::TopicNotification; -use sp_api::{ApiRef, ProvideRuntimeApi}; -use sp_runtime::traits::Block as BlockT; -use sp_core::{crypto::Pair, testing::TaskExecutor}; -use sp_keyring::Sr25519Keyring; - -use futures::executor::LocalPool; -use futures::task::LocalSpawnExt; - -#[derive(Default)] -pub struct MockNetworkOps { - recorded: Mutex, -} - -#[derive(Default)] -struct Recorded { - peer_reputations: HashMap, - notifications: Vec<(PeerId, Message)>, -} - -// Test setup registers receivers of gossip messages as well as signals that -// fire when they are taken. -type GossipStreamEntry = (mpsc::UnboundedReceiver, oneshot::Sender<()>); - -#[derive(Default, Clone)] -struct MockGossip { - inner: Arc>>, - gossip_messages: Arc>>, -} - -impl MockGossip { - fn add_gossip_stream(&self, topic: Hash) - -> (mpsc::UnboundedSender, oneshot::Receiver<()>) - { - let (tx, rx) = mpsc::unbounded(); - let (o_tx, o_rx) = oneshot::channel(); - self.inner.lock().insert(topic, (rx, o_tx)); - (tx, o_rx) - } -} - -impl NetworkServiceOps for MockNetworkOps { - fn report_peer(&self, peer: PeerId, value: sc_network::ReputationChange) { - let mut recorded = self.recorded.lock(); - let total_rep = recorded.peer_reputations.entry(peer).or_insert(0); - - *total_rep = total_rep.saturating_add(value.value); - } - - fn write_notification( - &self, - peer: PeerId, - engine_id: ConsensusEngineId, - notification: Vec, - ) { - assert_eq!(engine_id, POLKADOT_ENGINE_ID); - let message = Message::decode(&mut ¬ification[..]).expect("invalid notification"); - self.recorded.lock().notifications.push((peer, message)); - } -} - -impl crate::legacy::GossipService for MockGossip { - fn gossip_messages_for(&self, topic: Hash) -> crate::legacy::GossipMessageStream { - crate::legacy::GossipMessageStream::new(match self.inner.lock().remove(&topic) { - None => Box::pin(stream::empty()), - Some((rx, o_rx)) => { - let _ = o_rx.send(()); - Box::pin(rx) - } - }) - } - - fn gossip_message(&self, topic: Hash, message: GossipMessage) { - self.gossip_messages.lock().insert(topic, message); - } - - fn send_message(&self, _who: PeerId, _message: GossipMessage) { - - } -} - -impl GossipOps for MockGossip { - fn new_local_leaf(&self, _: crate::legacy::gossip::MessageValidationData) -> crate::legacy::gossip::NewLeafActions { - crate::legacy::gossip::NewLeafActions::new() - } - - fn register_availability_store(&self, _store: av_store::Store) {} -} - -#[derive(Default)] -struct ApiData { - validators: Vec, - duties: Vec, - active_parachains: Vec<(ParaId, Option<(CollatorId, Retriable)>)>, -} - -#[derive(Default, Clone)] -struct TestApi { - data: Arc>, -} - -#[derive(Default)] -struct RuntimeApi { - data: Arc>, -} - -impl ProvideRuntimeApi for TestApi { - type Api = RuntimeApi; - - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { - RuntimeApi { data: self.data.clone() }.into() - } -} - -sp_api::mock_impl_runtime_apis! { - impl ParachainHost for RuntimeApi { - type Error = sp_blockchain::Error; - - fn validators(&self) -> Vec { - self.data.lock().validators.clone() - } - - fn duty_roster(&self) -> DutyRoster { - DutyRoster { - validator_duty: self.data.lock().duties.clone(), - } - } - - fn active_parachains(&self) -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { - self.data.lock().active_parachains.clone() - } - - fn parachain_code(_: ParaId) -> Option { - Some(ValidationCode(Vec::new())) - } - - fn global_validation_data() -> GlobalValidationData { - Default::default() - } - - fn local_validation_data(_: ParaId) -> Option { - Some(Default::default()) - } - - fn get_heads(_: Vec<::Extrinsic>) -> Option> { - Some(Vec::new()) - } - - fn signing_context() -> SigningContext { - SigningContext { - session_index: Default::default(), - parent_hash: Default::default(), - } - } - fn downward_messages(_: ParaId) -> Vec { - Vec::new() - } - } -} - -impl super::Service { - async fn connect_peer(&mut self, peer: PeerId, role: ObservedRole) { - self.sender.send(ServiceToWorkerMsg::PeerConnected(peer, role)).await.unwrap(); - } - - async fn peer_message(&mut self, peer: PeerId, message: Message) { - let bytes = message.encode().into(); - - self.sender.send(ServiceToWorkerMsg::PeerMessage(peer, vec![bytes])).await.unwrap(); - } - - async fn disconnect_peer(&mut self, peer: PeerId) { - self.sender.send(ServiceToWorkerMsg::PeerDisconnected(peer)).await.unwrap(); - } - - async fn synchronize( - &mut self, - callback: impl FnOnce(&mut ProtocolHandler) -> T + Send + 'static, - ) -> T { - let (tx, rx) = oneshot::channel(); - - let msg = ServiceToWorkerMsg::Synchronize(Box::new(move |proto| { - let res = callback(proto); - if let Err(_) = tx.send(res) { - log::warn!(target: "p_net", "Failed to send synchronization result"); - } - })); - - self.sender.send(msg).await.expect("Worker thread unexpectedly hung up"); - rx.await.expect("Worker thread failed to send back result") - } -} - -fn test_setup(config: Config) -> ( - Service, - MockGossip, - LocalPool, - impl Future + 'static, -) { - let pool = LocalPool::new(); - - let network_ops = Arc::new(MockNetworkOps::default()); - let mock_gossip = MockGossip::default(); - let (worker_tx, worker_rx) = mpsc::channel(0); - let api = Arc::new(TestApi::default()); - - let worker_task = worker_loop( - config, - network_ops.clone(), - mock_gossip.clone(), - api.clone(), - worker_rx, - TaskExecutor::new(), - ); - - let service = Service { - sender: worker_tx, - network_service: network_ops, - }; - - (service, mock_gossip, pool, worker_task) -} - -#[test] -fn worker_task_shuts_down_when_sender_dropped() { - let (service, _gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); - - drop(service); - let _ = pool.run_until(worker_task); -} - -/// Given the async nature of `select!` that is being used in the main loop of the worker -/// and that consensus instances use their own channels, we don't know when the synchronize message -/// is handled. This helper functions checks multiple times that the given instance is dropped. Even -/// if the first round fails, the second one should be successful as the consensus instance drop -/// should be already handled this time. -fn wait_for_instance_drop(service: &mut Service, pool: &mut LocalPool, instance: Hash) { - let mut try_counter = 0; - let max_tries = 3; - - while try_counter < max_tries { - let dropped = pool.run_until(service.synchronize(move |proto| { - !proto.consensus_instances.contains_key(&instance) - })); - - if dropped { - return; - } - - try_counter += 1; - } - - panic!("Consensus instance `{}` wasn't dropped!", instance); -} - -#[test] -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 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, - None, - )); - - pool.spawner().spawn_local(worker_task).unwrap(); - - let router = pool.run_until( - service.build_table_router(table, &[]) - ).unwrap(); - - drop(router); - - wait_for_instance_drop(&mut service, &mut pool, relay_parent); -} - -#[test] -fn collation_is_received_with_dropped_router() { - let (mut service, gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); - let relay_parent = [0; 32].into(); - let topic = crate::legacy::gossip::attestation_topic(relay_parent); - - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash: relay_parent, - }; - let table = Arc::new(SharedTable::new( - vec![Sr25519Keyring::Alice.public().into()], - HashMap::new(), - Some(Arc::new(Sr25519Keyring::Alice.pair().into())), - signing_context, - AvailabilityStore::new_in_memory(service.clone()), - None, - None, - )); - - pool.spawner().spawn_local(worker_task).unwrap(); - - let router = pool.run_until( - service.build_table_router(table, &[]) - ).unwrap(); - - let receipt = AbridgedCandidateReceipt { relay_parent, ..Default::default() }; - let local_collation_future = router.local_collation( - receipt, - PoVBlock { block_data: BlockData(Vec::new()) }, - (0, &[]), - ); - - // Drop the router and make sure that the consensus instance is still alive - drop(router); - - assert!(pool.run_until(service.synchronize(move |proto| { - proto.consensus_instances.contains_key(&relay_parent) - }))); - - // The gossip message should still be unknown - assert!(!gossip.gossip_messages.lock().contains_key(&topic)); - - pool.run_until(local_collation_future).unwrap(); - - // Make sure the instance is now dropped and the message was gossiped - wait_for_instance_drop(&mut service, &mut pool, relay_parent); - assert!(pool.run_until(service.synchronize(move |_| { - gossip.gossip_messages.lock().contains_key(&topic) - }))); -} - -#[test] -fn validator_peer_cleaned_up() { - let (mut service, _gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); - - let peer = PeerId::random(); - let validator_key = Sr25519Keyring::Alice.pair(); - let validator_id = ValidatorId::from(validator_key.public()); - - pool.spawner().spawn_local(worker_task).unwrap(); - pool.run_until(async move { - service.connect_peer(peer.clone(), ObservedRole::Authority).await; - service.peer_message(peer.clone(), Message::Status(Status { - version: VERSION, - collating_for: None, - })).await; - service.peer_message(peer.clone(), Message::ValidatorId(validator_id.clone())).await; - - let p = peer.clone(); - let v = validator_id.clone(); - let (peer_has_key, reverse_lookup) = service.synchronize(move |proto| { - let peer_has_key = proto.peers.get(&p).map_or( - false, - |p_data| p_data.session_keys.as_slice().contains(&v), - ); - - let reverse_lookup = proto.connected_validators.get(&v).map_or( - false, - |reps| reps.contains(&p), - ); - - (peer_has_key, reverse_lookup) - }).await; - - assert!(peer_has_key); - assert!(reverse_lookup); - - service.disconnect_peer(peer.clone()).await; - - let p = peer.clone(); - let v = validator_id.clone(); - let (peer_removed, rev_removed) = service.synchronize(move |proto| { - let peer_removed = !proto.peers.contains_key(&p); - let reverse_mapping_removed = !proto.connected_validators.contains_key(&v); - - (peer_removed, reverse_mapping_removed) - }).await; - - assert!(peer_removed); - assert!(rev_removed); - }); -} - -#[test] -fn validator_key_spillover_cleaned() { - let (mut service, _gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); - - let peer = PeerId::random(); - let make_validator_id = |ring: Sr25519Keyring| ValidatorId::from(ring.public()); - - // We will push 1 extra beyond what is normally kept. - assert_eq!(RECENT_SESSIONS, 3); - let key_a = make_validator_id(Sr25519Keyring::Alice); - let key_b = make_validator_id(Sr25519Keyring::Bob); - let key_c = make_validator_id(Sr25519Keyring::Charlie); - let key_d = make_validator_id(Sr25519Keyring::Dave); - - let keys = vec![key_a, key_b, key_c, key_d]; - - pool.spawner().spawn_local(worker_task).unwrap(); - pool.run_until(async move { - service.connect_peer(peer.clone(), ObservedRole::Authority).await; - service.peer_message(peer.clone(), Message::Status(Status { - version: VERSION, - collating_for: None, - })).await; - - for key in &keys { - service.peer_message(peer.clone(), Message::ValidatorId(key.clone())).await; - } - - let p = peer.clone(); - let active_keys = keys[1..].to_vec(); - let discarded_key = keys[0].clone(); - assert!(service.synchronize(move |proto| { - let active_correct = proto.peers.get(&p).map_or(false, |p_data| { - p_data.session_keys.as_slice() == &active_keys[..] - }); - - let active_lookup = active_keys.iter().all(|k| { - proto.connected_validators.get(&k).map_or(false, |m| m.contains(&p)) - }); - - let discarded = !proto.connected_validators.contains_key(&discarded_key); - - active_correct && active_lookup && discarded - }).await); - }); -} - -#[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, - 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(); -} - -#[test] -fn validator_sends_key_and_role_to_collator_on_status() { - let (service, _gossip, mut pool, worker_task) = test_setup(Config { collating_for: None }); - - let peer = PeerId::random(); - let peer_clone = peer.clone(); - let validator_key = Sr25519Keyring::Alice.pair(); - let validator_id = ValidatorId::from(validator_key.public()); - let validator_id_clone = validator_id.clone(); - let collator_id = CollatorId::from(Sr25519Keyring::Bob.public()); - let para_id = ParaId::from(100); - let mut service_clone = service.clone(); - - pool.spawner().spawn_local(worker_task).unwrap(); - pool.run_until(async move { - service_clone.synchronize(move |proto| { proto.local_keys.insert(validator_id_clone); }).await; - service_clone.connect_peer(peer_clone.clone(), ObservedRole::Authority).await; - service_clone.peer_message(peer_clone.clone(), Message::Status(Status { - version: VERSION, - collating_for: Some((collator_id, para_id)), - })).await; - }); - - let expected_msg = Message::ValidatorId(validator_id.clone()); - let validator_id_pos = service.network_service.recorded.lock().notifications.iter().position(|(p, notification)| { - peer == *p && *notification == expected_msg - }); - - let expected_msg = Message::CollatorRole(CollatorRole::Primary); - let collator_role_pos = service.network_service.recorded.lock().notifications.iter().position(|(p, notification)| { - peer == *p && *notification == expected_msg - }); - - assert!(validator_id_pos < collator_role_pos); -} - -#[test] -fn collator_state_send_key_updates_state_correctly() { - let mut state = CollatorState::Fresh; - state.send_key(Sr25519Keyring::Alice.public().into(), |_| {}); - assert!(matches!(state, CollatorState::Primed(None))); - - let mut state = CollatorState::RolePending(CollatorRole::Primary); - - let mut counter = 0; - state.send_key(Sr25519Keyring::Alice.public().into(), |msg| { - match (counter, msg) { - (0, Message::ValidatorId(_)) => { - counter += 1; - }, - (1, Message::CollatorRole(CollatorRole::Primary)) => {}, - err @ _ => panic!("Unexpected message: {:?}", err), - } - }); - assert!(matches!(state, CollatorState::Primed(Some(CollatorRole::Primary)))); -} diff --git a/network/test/Cargo.toml b/network/test/Cargo.toml deleted file mode 100644 index d59123fad98e..000000000000 --- a/network/test/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "polkadot-network-test" -version = "0.8.22" -license = "GPL-3.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -log = "0.4.8" -parking_lot = "0.10.0" -futures = "0.3.1" -rand = "0.7.2" -sc-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-service = { git = "https://github.com/paritytech/substrate", features = ["test-helpers"], branch = "master" } -sc-network-test = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -sc-block-builder = { git = "https://github.com/paritytech/substrate", branch = "master" } -polkadot-test-runtime-client = { path = "../../runtime/test-runtime/client" } diff --git a/network/test/src/block_import.rs b/network/test/src/block_import.rs deleted file mode 100644 index 4ecc38a537f6..000000000000 --- a/network/test/src/block_import.rs +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2020 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 . - -//! Testing block import logic. - -use sp_consensus::ImportedAux; -use sp_consensus::import_queue::{ - import_single_block, BasicQueue, BlockImportError, BlockImportResult, IncomingBlock, -}; -use polkadot_test_runtime_client::{self, prelude::*}; -use polkadot_test_runtime_client::runtime::{Block, Hash}; -use sp_runtime::generic::BlockId; -use super::*; - -fn prepare_good_block() -> (TestClient, Hash, u64, PeerId, IncomingBlock) { - let mut client = polkadot_test_runtime_client::new(); - let mut builder = client.new_block(Default::default()).unwrap(); - - let extrinsics = polkadot_test_runtime_client::needed_extrinsics(0); - - for extrinsic in &extrinsics { - builder.push(extrinsic.clone()).unwrap(); - } - - let block = builder.build().unwrap().block; - client.import(BlockOrigin::File, block).unwrap(); - - let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1); - let header = client.header(&BlockId::Number(1)).unwrap(); - let justification = client.justification(&BlockId::Number(1)).unwrap(); - let peer_id = PeerId::random(); - (client, hash, number, peer_id.clone(), IncomingBlock { - hash, - header, - body: Some(extrinsics), - justification, - origin: Some(peer_id.clone()), - allow_missing_state: false, - import_existing: false, - }) -} - -#[test] -fn import_single_good_block_works() { - let (_, _hash, number, peer_id, block) = prepare_good_block(); - - let mut expected_aux = ImportedAux::default(); - expected_aux.is_new_best = true; - - match import_single_block(&mut polkadot_test_runtime_client::new(), BlockOrigin::File, block, &mut PassThroughVerifier::new(true)) { - Ok(BlockImportResult::ImportedUnknown(ref num, ref aux, ref org)) - if *num == number as u32 && *aux == expected_aux && *org == Some(peer_id) => {} - r @ _ => panic!("{:?}", r) - } -} - -#[test] -fn import_single_good_known_block_is_ignored() { - let (mut client, _hash, number, _, block) = prepare_good_block(); - match import_single_block(&mut client, BlockOrigin::File, block, &mut PassThroughVerifier::new(true)) { - Ok(BlockImportResult::ImportedKnown(ref n)) if *n == number as u32 => {} - _ => panic!() - } -} - -#[test] -fn import_single_good_block_without_header_fails() { - let (_, _, _, peer_id, mut block) = prepare_good_block(); - block.header = None; - match import_single_block(&mut polkadot_test_runtime_client::new(), BlockOrigin::File, block, &mut PassThroughVerifier::new(true)) { - Err(BlockImportError::IncompleteHeader(ref org)) if *org == Some(peer_id) => {} - _ => panic!() - } -} - -#[test] -fn async_import_queue_drops() { - let executor = sp_core::testing::TaskExecutor::new(); - // Perform this test multiple times since it exhibits non-deterministic behavior. - for _ in 0..100 { - let verifier = PassThroughVerifier::new(true); - - let queue = BasicQueue::new( - verifier, - Box::new(polkadot_test_runtime_client::new()), - None, - None, - &executor, - None - ); - drop(queue); - } -} diff --git a/network/test/src/lib.rs b/network/test/src/lib.rs deleted file mode 100644 index 4978db5834c8..000000000000 --- a/network/test/src/lib.rs +++ /dev/null @@ -1,884 +0,0 @@ -// Copyright 2020 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 . - -#![allow(missing_docs)] - -#[cfg(test)] -mod block_import; - -use std::{collections::HashMap, pin::Pin, sync::Arc, marker::PhantomData, task::{Poll, Context as FutureContext}}; - -use log::trace; -use sc_network::config::{build_multiaddr, FinalityProofProvider, Role}; -use sp_blockchain::{ - Result as ClientResult, well_known_cache_keys::{self, Id as CacheKeyId}, Info as BlockchainInfo, - HeaderBackend, -}; -use sc_client_api::{ - BlockchainEvents, BlockImportNotification, - FinalityNotifications, ImportNotifications, - FinalityNotification, - client::BlockBackend, - backend::{TransactionFor, AuxStore, Backend, Finalizer}, -}; -use sc_consensus::LongestChain; -use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; -use sp_consensus::block_validation::DefaultBlockAnnounceValidator; -use sp_consensus::import_queue::{ - BasicQueue, BoxJustificationImport, Verifier, BoxFinalityProofImport, -}; -use sp_consensus::block_import::{BlockImport, ImportResult}; -use sp_consensus::Error as ConsensusError; -use sp_consensus::{BlockOrigin, BlockImportParams, BlockCheckParams, JustificationImport}; -use futures::prelude::*; -use sc_network::{NetworkWorker, NetworkService, config::ProtocolId}; -use sc_network::config::{ - NetworkConfiguration, TransportConfig, BoxFinalityProofRequestBuilder, TransactionImport, - TransactionImportFuture -}; -use parking_lot::Mutex; -use sp_core::H256; -use sc_network::{PeerId, config::{ProtocolConfig, TransactionPool}}; -use sp_runtime::generic::BlockId; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; -use sp_runtime::Justification; -pub use sc_network_test::PassThroughVerifier; -use sc_service::client::Client; - -pub use polkadot_test_runtime_client::runtime::{Block, Extrinsic, Hash}; -pub use polkadot_test_runtime_client::{TestClient, TestClientBuilder, TestClientBuilderExt}; - -pub type PeersFullClient = Client< - polkadot_test_runtime_client::Backend, - polkadot_test_runtime_client::Executor, - Block, - polkadot_test_runtime_client::runtime::RuntimeApi ->; -pub type PeersLightClient = Client< - polkadot_test_runtime_client::LightBackend, - polkadot_test_runtime_client::LightExecutor, - Block, - polkadot_test_runtime_client::runtime::RuntimeApi ->; - -#[derive(Clone)] -pub enum PeersClient { - Full(Arc, Arc), - Light(Arc, Arc), -} - -impl PeersClient { - pub fn as_full(&self) -> Option> { - match *self { - PeersClient::Full(ref client, ref _backend) => Some(client.clone()), - _ => None, - } - } - - pub fn as_block_import(&self) -> BlockImportAdapter { - match *self { - PeersClient::Full(ref client, ref _backend) => - BlockImportAdapter::new_full(client.clone()), - PeersClient::Light(ref client, ref _backend) => - BlockImportAdapter::Light(Arc::new(Mutex::new(client.clone())), PhantomData), - } - } - - pub fn get_aux(&self, key: &[u8]) -> ClientResult>> { - match *self { - PeersClient::Full(ref client, ref _backend) => client.get_aux(key), - PeersClient::Light(ref client, ref _backend) => client.get_aux(key), - } - } - - pub fn info(&self) -> BlockchainInfo { - match *self { - PeersClient::Full(ref client, ref _backend) => client.chain_info(), - PeersClient::Light(ref client, ref _backend) => client.chain_info(), - } - } - - pub fn header(&self, block: &BlockId) -> ClientResult::Header>> { - match *self { - PeersClient::Full(ref client, ref _backend) => client.header(block), - PeersClient::Light(ref client, ref _backend) => client.header(block), - } - } - - pub fn justification(&self, block: &BlockId) -> ClientResult> { - match *self { - PeersClient::Full(ref client, ref _backend) => client.justification(block), - PeersClient::Light(ref client, ref _backend) => client.justification(block), - } - } - - pub fn finality_notification_stream(&self) -> FinalityNotifications { - match *self { - PeersClient::Full(ref client, ref _backend) => client.finality_notification_stream(), - PeersClient::Light(ref client, ref _backend) => client.finality_notification_stream(), - } - } - - pub fn import_notification_stream(&self) -> ImportNotifications{ - match *self { - PeersClient::Full(ref client, ref _backend) => client.import_notification_stream(), - PeersClient::Light(ref client, ref _backend) => client.import_notification_stream(), - } - } - - pub fn finalize_block( - &self, - id: BlockId, - justification: Option, - notify: bool - ) -> ClientResult<()> { - match *self { - PeersClient::Full(ref client, ref _backend) => client.finalize_block(id, justification, notify), - PeersClient::Light(ref client, ref _backend) => client.finalize_block(id, justification, notify), - } - } -} - -pub struct Peer { - pub data: D, - client: PeersClient, - /// We keep a copy of the verifier so that we can invoke it for locally-generated blocks, - /// instead of going through the import queue. - verifier: VerifierAdapter, - /// We keep a copy of the block_import so that we can invoke it for locally-generated blocks, - /// instead of going through the import queue. - block_import: BlockImportAdapter<()>, - select_chain: Option>, - backend: Option>, - network: NetworkWorker::Hash>, - imported_blocks_stream: Pin> + Send>>, - finality_notification_stream: Pin> + Send>>, -} - -impl Peer { - /// Get this peer ID. - pub fn id(&self) -> &PeerId { - self.network.service().local_peer_id() - } - - /// Returns true if we're major syncing. - pub fn is_major_syncing(&self) -> bool { - self.network.service().is_major_syncing() - } - - // Returns a clone of the local SelectChain, only available on full nodes - pub fn select_chain(&self) -> Option> { - self.select_chain.clone() - } - - /// Returns the number of peers we're connected to. - pub fn num_peers(&self) -> usize { - self.network.num_connected_peers() - } - - /// Returns true if we have no peer. - pub fn is_offline(&self) -> bool { - self.num_peers() == 0 - } - - /// Request a justification for the given block. - pub fn request_justification(&self, hash: &::Hash, number: NumberFor) { - self.network.service().request_justification(hash, number); - } - - /// Announces an important block on the network. - pub fn announce_block(&self, hash: ::Hash, data: Vec) { - self.network.service().announce_block(hash, data); - } - - /// Request explicit fork sync. - pub fn set_sync_fork_request(&self, peers: Vec, hash: ::Hash, number: NumberFor) { - self.network.service().set_sync_fork_request(peers, hash, number); - } - - /// Add blocks to the peer -- edit the block before adding - pub fn generate_blocks(&mut self, count: usize, origin: BlockOrigin, edit_block: F) -> H256 - where F: FnMut(BlockBuilder) -> Block - { - let best_hash = self.client.info().best_hash; - self.generate_blocks_at(BlockId::Hash(best_hash), count, origin, edit_block, false) - } - - /// Add blocks to the peer -- edit the block before adding. The chain will - /// start at the given block iD. - fn generate_blocks_at( - &mut self, - at: BlockId, - count: usize, - origin: BlockOrigin, - mut edit_block: F, - headers_only: bool, - ) -> H256 where F: FnMut(BlockBuilder) -> Block { - let full_client = self.client.as_full() - .expect("blocks could only be generated by full clients"); - let mut at = full_client.header(&at).unwrap().unwrap().hash(); - for _ in 0..count { - let builder = full_client.new_block_at( - &BlockId::Hash(at), - Default::default(), - false, - ).unwrap(); - let block = edit_block(builder); - let hash = block.header.hash(); - trace!( - target: "test_network", - "Generating {}, (#{}, parent={})", - hash, - block.header.number, - block.header.parent_hash, - ); - let header = block.header.clone(); - let (import_block, cache) = self.verifier.verify( - origin, - header.clone(), - None, - if headers_only { None } else { Some(block.extrinsics) }, - ).unwrap(); - let cache = if let Some(cache) = cache { - cache.into_iter().collect() - } else { - Default::default() - }; - self.block_import.import_block(import_block, cache).expect("block_import failed"); - at = hash; - } - - self.network.update_chain(); - self.network.service().announce_block(at.clone(), Vec::new()); - at - } - - /// Push blocks to the peer (simplified: with or without a TX) - pub fn push_blocks(&mut self, count: usize, with_tx: bool) -> H256 { - let best_hash = self.client.info().best_hash; - self.push_blocks_at(BlockId::Hash(best_hash), count, with_tx) - } - - /// Push blocks to the peer (simplified: with or without a TX) - pub fn push_headers(&mut self, count: usize) -> H256 { - let best_hash = self.client.info().best_hash; - self.generate_tx_blocks_at(BlockId::Hash(best_hash), count, false, true) - } - - /// Push blocks to the peer (simplified: with or without a TX) starting from - /// given hash. - pub fn push_blocks_at(&mut self, at: BlockId, count: usize, with_tx: bool) -> H256 { - self.generate_tx_blocks_at(at, count, with_tx, false) - } - - /// Push blocks/headers to the peer (simplified: with or without a TX) starting from - /// given hash. - fn generate_tx_blocks_at(&mut self, at: BlockId, count: usize, with_tx: bool, headers_only:bool) -> H256 { - if with_tx { - self.generate_blocks_at( - at, - count, - BlockOrigin::File, - |builder| builder.build().unwrap().block, - headers_only - ) - } else { - self.generate_blocks_at( - at, - count, - BlockOrigin::File, - |builder| builder.build().unwrap().block, - headers_only, - ) - } - } - - /// Get a reference to the client. - pub fn client(&self) -> &PeersClient { - &self.client - } - - /// Get a reference to the network service. - pub fn network_service(&self) -> &Arc::Hash>> { - &self.network.service() - } - - /// Test helper to compare the blockchain state of multiple (networked) - /// clients. - /// Potentially costly, as it creates in-memory copies of both blockchains in order - /// to compare them. If you have easier/softer checks that are sufficient, e.g. - /// by using .info(), you should probably use it instead of this. - pub fn blockchain_canon_equals(&self, other: &Self) -> bool { - if let (Some(mine), Some(others)) = (self.backend.clone(), other.backend.clone()) { - mine.blockchain().info().best_hash == others.blockchain().info().best_hash - } else { - false - } - } - - /// Count the total number of imported blocks. - pub fn blocks_count(&self) -> u64 { - self.backend.as_ref().map( - |backend| backend.blockchain().info().best_number as u64 - ).unwrap_or(0) - } - - /// Return a collection of block hashes that failed verification - pub fn failed_verifications(&self) -> HashMap<::Hash, String> { - self.verifier.failed_verifications.lock().clone() - } -} - -pub struct EmptyTransactionPool; - -impl TransactionPool for EmptyTransactionPool { - fn transactions(&self) -> Vec<(Hash, Extrinsic)> { - Vec::new() - } - - fn hash_of(&self, _transaction: &Extrinsic) -> Hash { - Hash::default() - } - - fn import(&self, _transaction: Extrinsic) -> TransactionImportFuture { - Box::pin(futures::future::ready(TransactionImport::None)) - } - - fn on_broadcasted(&self, _: HashMap>) {} - - fn transaction(&self, _h: &Hash) -> Option { None } -} - -/// Implements `BlockImport` for any `Transaction`. Internally the transaction is -/// "converted", aka the field is set to `None`. -/// -/// This is required as the `TestNetFactory` trait does not distinguish between -/// full and light nodes. -pub enum BlockImportAdapter { - Full( - Arc, - Error = ConsensusError - > + Send>>, - PhantomData, - ), - Light( - Arc, - Error = ConsensusError - > + Send>>, - PhantomData, - ), -} - -impl BlockImportAdapter { - /// Create a new instance of `Self::Full`. - pub fn new_full( - full: impl BlockImport< - Block, - Transaction = TransactionFor, - Error = ConsensusError - > - + 'static - + Send - ) -> Self { - Self::Full(Arc::new(Mutex::new(full)), PhantomData) - } - - /// Create a new instance of `Self::Light`. - pub fn new_light( - light: impl BlockImport< - Block, - Transaction = TransactionFor, - Error = ConsensusError - > - + 'static - + Send - ) -> Self { - Self::Light(Arc::new(Mutex::new(light)), PhantomData) - } -} - -impl Clone for BlockImportAdapter { - fn clone(&self) -> Self { - match self { - Self::Full(full, _) => Self::Full(full.clone(), PhantomData), - Self::Light(light, _) => Self::Light(light.clone(), PhantomData), - } - } -} - -impl BlockImport for BlockImportAdapter { - type Error = ConsensusError; - type Transaction = Transaction; - - fn check_block( - &mut self, - block: BlockCheckParams, - ) -> Result { - match self { - Self::Full(full, _) => full.lock().check_block(block), - Self::Light(light, _) => light.lock().check_block(block), - } - } - - fn import_block( - &mut self, - block: BlockImportParams, - cache: HashMap>, - ) -> Result { - match self { - Self::Full(full, _) => full.lock().import_block(block.convert_transaction(), cache), - Self::Light(light, _) => light.lock().import_block(block.convert_transaction(), cache), - } - } -} - -/// Implements `Verifier` on an `Arc>`. Used internally. -#[derive(Clone)] -struct VerifierAdapter { - verifier: Arc>>>, - failed_verifications: Arc>>, -} - -impl Verifier for VerifierAdapter { - fn verify( - &mut self, - origin: BlockOrigin, - header: B::Header, - justification: Option, - body: Option> - ) -> Result<(BlockImportParams, Option)>>), String> { - let hash = header.hash(); - self.verifier.lock().verify(origin, header, justification, body).map_err(|e| { - self.failed_verifications.lock().insert(hash, e.clone()); - e - }) - } -} - -impl VerifierAdapter { - fn new(verifier: Arc>>>) -> VerifierAdapter { - VerifierAdapter { - verifier, - failed_verifications: Default::default(), - } - } -} - -pub trait TestNetFactory: Sized { - type Verifier: 'static + Verifier; - type PeerData: Default; - - /// These two need to be implemented! - fn from_config(config: &ProtocolConfig) -> Self; - fn make_verifier( - &self, - client: PeersClient, - config: &ProtocolConfig, - peer_data: &Self::PeerData, - ) -> Self::Verifier; - - /// Get reference to peer. - fn peer(&mut self, i: usize) -> &mut Peer; - fn peers(&self) -> &Vec>; - fn mut_peers>)>( - &mut self, - closure: F, - ); - - /// Get custom block import handle for fresh client, along with peer data. - fn make_block_import(&self, client: PeersClient) - -> ( - BlockImportAdapter, - Option>, - Option>, - Option>, - Self::PeerData, - ) - { - (client.as_block_import(), None, None, None, Default::default()) - } - - /// Get finality proof provider (if supported). - fn make_finality_proof_provider( - &self, - _client: PeersClient, - ) -> Option>> { - None - } - - fn default_config() -> ProtocolConfig { - ProtocolConfig::default() - } - - /// Create new test network with this many peers. - fn new(n: usize) -> Self { - trace!(target: "test_network", "Creating test network"); - let mut net = Self::from_config(&Default::default()); - - for i in 0..n { - trace!(target: "test_network", "Adding peer {}", i); - net.add_full_peer(); - } - net - } - - fn add_full_peer(&mut self,) { - self.add_full_peer_with_states(None) - } - - /// Add a full peer. - fn add_full_peer_with_states(&mut self, keep_blocks: Option) { - let test_client_builder = match keep_blocks { - Some(keep_blocks) => TestClientBuilder::with_pruning_window(keep_blocks), - None => TestClientBuilder::with_default_backend(), - }; - let backend = test_client_builder.backend(); - let (c, longest_chain) = test_client_builder.build_with_longest_chain(); - let client = Arc::new(c); - - let ( - block_import, - justification_import, - finality_proof_import, - finality_proof_request_builder, - data, - ) = self.make_block_import(PeersClient::Full(client.clone(), backend.clone())); - - let verifier = self.make_verifier( - PeersClient::Full(client.clone(), backend.clone()), - &Default::default(), - &data, - ); - let verifier = VerifierAdapter::new(Arc::new(Mutex::new(Box::new(verifier) as Box<_>))); - - let import_queue = Box::new(BasicQueue::new( - verifier.clone(), - Box::new(block_import.clone()), - justification_import, - finality_proof_import, - &sp_core::testing::TaskExecutor::new(), - None, - )); - - let listen_addr = build_multiaddr![Memory(rand::random::())]; - - let mut network_config = NetworkConfiguration::new( - "test-node", - "test-client", - Default::default(), - None, - ); - network_config.listen_addresses = vec![listen_addr.clone()]; - network_config.transport = TransportConfig::MemoryOnly; - let network = NetworkWorker::new(sc_network::config::Params { - role: Role::Full, - executor: None, - network_config, - chain: client.clone(), - finality_proof_provider: self.make_finality_proof_provider( - PeersClient::Full(client.clone(), backend.clone()), - ), - finality_proof_request_builder, - on_demand: None, - transaction_pool: Arc::new(EmptyTransactionPool), - protocol_id: ProtocolId::from(&b"test-protocol-name"[..]), - import_queue, - block_announce_validator: Box::new(DefaultBlockAnnounceValidator), - metrics_registry: None, - }).unwrap(); - - self.mut_peers(|peers| { - for peer in peers.iter_mut() { - peer.network.add_known_address(network.service().local_peer_id().clone(), listen_addr.clone()); - } - - let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); - let finality_notification_stream = Box::pin(client.finality_notification_stream().fuse()); - - peers.push(Peer { - data, - client: PeersClient::Full(client, backend.clone()), - select_chain: Some(longest_chain), - backend: Some(backend), - imported_blocks_stream, - finality_notification_stream, - block_import, - verifier, - network, - }); - }); - } - - /// Add a light peer. - fn add_light_peer(&mut self) { - let (c, backend) = polkadot_test_runtime_client::new_light(); - let client = Arc::new(c); - let ( - block_import, - justification_import, - finality_proof_import, - finality_proof_request_builder, - data, - ) = self.make_block_import(PeersClient::Light(client.clone(), backend.clone())); - - let verifier = self.make_verifier( - PeersClient::Light(client.clone(), backend.clone()), - &Default::default(), - &data, - ); - let verifier = VerifierAdapter::new(Arc::new(Mutex::new(Box::new(verifier) as Box<_>))); - - let import_queue = Box::new(BasicQueue::new( - verifier.clone(), - Box::new(block_import.clone()), - justification_import, - finality_proof_import, - &sp_core::testing::TaskExecutor::new(), - None, - )); - - let listen_addr = build_multiaddr![Memory(rand::random::())]; - - let mut network_config = NetworkConfiguration::new( - "test-node", - "test-client", - Default::default(), - None, - ); - network_config.listen_addresses = vec![listen_addr.clone()]; - network_config.transport = TransportConfig::MemoryOnly; - let network = NetworkWorker::new(sc_network::config::Params { - role: Role::Full, - executor: None, - network_config, - chain: client.clone(), - finality_proof_provider: self.make_finality_proof_provider( - PeersClient::Light(client.clone(), backend.clone()) - ), - finality_proof_request_builder, - on_demand: None, - transaction_pool: Arc::new(EmptyTransactionPool), - protocol_id: ProtocolId::from(&b"test-protocol-name"[..]), - import_queue, - block_announce_validator: Box::new(DefaultBlockAnnounceValidator), - metrics_registry: None, - }).unwrap(); - - self.mut_peers(|peers| { - for peer in peers.iter_mut() { - peer.network.add_known_address(network.service().local_peer_id().clone(), listen_addr.clone()); - } - - let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); - let finality_notification_stream = Box::pin(client.finality_notification_stream().fuse()); - - peers.push(Peer { - data, - verifier, - select_chain: None, - backend: None, - block_import, - client: PeersClient::Light(client, backend), - imported_blocks_stream, - finality_notification_stream, - network, - }); - }); - } - - /// Polls the testnet until all nodes are in sync. - /// - /// Must be executed in a task context. - fn poll_until_sync(&mut self, cx: &mut FutureContext) -> Poll<()> { - self.poll(cx); - - // Return `NotReady` if there's a mismatch in the highest block number. - let mut highest = None; - for peer in self.peers().iter() { - if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 { - return Poll::Pending - } - if peer.network.num_sync_requests() != 0 { - return Poll::Pending - } - match (highest, peer.client.info().best_hash) { - (None, b) => highest = Some(b), - (Some(ref a), ref b) if a == b => {}, - (Some(_), _) => return Poll::Pending - } - } - Poll::Ready(()) - } - - /// Polls the testnet until theres' no activiy of any kind. - /// - /// Must be executed in a task context. - fn poll_until_idle(&mut self, cx: &mut FutureContext) -> Poll<()> { - self.poll(cx); - - for peer in self.peers().iter() { - if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 { - return Poll::Pending - } - if peer.network.num_sync_requests() != 0 { - return Poll::Pending - } - } - Poll::Ready(()) - } - - /// Blocks the current thread until we are sync'ed. - /// - /// Calls `poll_until_sync` repeatedly. - fn block_until_sync(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx))); - } - - /// Blocks the current thread until there are no pending packets. - /// - /// Calls `poll_until_idle` repeatedly with the runtime passed as parameter. - fn block_until_idle(&mut self) { - futures::executor::block_on(futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx))); - } - - /// Polls the testnet. Processes all the pending actions. - fn poll(&mut self, cx: &mut FutureContext) { - self.mut_peers(|peers| { - for peer in peers { - trace!(target: "sync", "-- Polling {}", peer.id()); - if let Poll::Ready(()) = peer.network.poll_unpin(cx) { - panic!("NetworkWorker has terminated unexpectedly.") - } - trace!(target: "sync", "-- Polling complete {}", peer.id()); - - // We poll `imported_blocks_stream`. - while let Poll::Ready(Some(notification)) = peer.imported_blocks_stream.as_mut().poll_next(cx) { - peer.network.service().announce_block(notification.hash, Vec::new()); - } - - // We poll `finality_notification_stream`, but we only take the last event. - let mut last = None; - while let Poll::Ready(Some(item)) = peer.finality_notification_stream.as_mut().poll_next(cx) { - last = Some(item); - } - if let Some(notification) = last { - peer.network.on_block_finalized(notification.hash, notification.header); - } - } - }); - } -} - -pub struct TestNet { - peers: Vec>, -} - -impl TestNetFactory for TestNet { - type Verifier = PassThroughVerifier; - type PeerData = (); - - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - TestNet { - peers: Vec::new(), - } - } - - fn make_verifier(&self, _client: PeersClient, _config: &ProtocolConfig, _peer_data: &()) - -> Self::Verifier - { - PassThroughVerifier::new(false) - } - - fn peer(&mut self, i: usize) -> &mut Peer<()> { - &mut self.peers[i] - } - - fn peers(&self) -> &Vec> { - &self.peers - } - - fn mut_peers>)>(&mut self, closure: F) { - closure(&mut self.peers); - } -} - -pub struct ForceFinalized(PeersClient); - -impl JustificationImport for ForceFinalized { - type Error = ConsensusError; - - fn import_justification( - &mut self, - hash: H256, - _number: NumberFor, - justification: Justification, - ) -> Result<(), Self::Error> { - self.0.finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| ConsensusError::InvalidJustification.into()) - } -} - -pub struct JustificationTestNet(TestNet); - -impl TestNetFactory for JustificationTestNet { - type Verifier = PassThroughVerifier; - type PeerData = (); - - fn from_config(config: &ProtocolConfig) -> Self { - JustificationTestNet(TestNet::from_config(config)) - } - - fn make_verifier(&self, client: PeersClient, config: &ProtocolConfig, peer_data: &()) -> Self::Verifier { - self.0.make_verifier(client, config, peer_data) - } - - fn peer(&mut self, i: usize) -> &mut Peer { - self.0.peer(i) - } - - fn peers(&self) -> &Vec> { - self.0.peers() - } - - fn mut_peers>, - )>(&mut self, closure: F) { - self.0.mut_peers(closure) - } - - fn make_block_import(&self, client: PeersClient) - -> ( - BlockImportAdapter, - Option>, - Option>, - Option>, - Self::PeerData, - ) - { - ( - client.as_block_import(), - Some(Box::new(ForceFinalized(client))), - None, - None, - Default::default(), - ) - } -} diff --git a/node/service/Cargo.toml b/node/service/Cargo.toml index 48b3ceba36f8..cf2dc0549a20 100644 --- a/node/service/Cargo.toml +++ b/node/service/Cargo.toml @@ -18,7 +18,6 @@ polkadot-overseer = { path = "../overseer" } polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../subsystem" } kusama-runtime = { path = "../../runtime/kusama" } westend-runtime = { path = "../../runtime/westend" } -polkadot-network = { path = "../../network", optional = true } polkadot-rpc = { path = "../../rpc" } polkadot-node-core-proposer = { path = "../core/proposer" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/node/test-service/Cargo.toml b/node/test-service/Cargo.toml index 46d54197a716..d1bf0aed1d19 100644 --- a/node/test-service/Cargo.toml +++ b/node/test-service/Cargo.toml @@ -13,9 +13,6 @@ rand = "0.7.3" tempfile = "3.1.0" # Polkadot dependencies -av_store = { package = "polkadot-availability-store", path = "../../availability-store" } -consensus = { package = "polkadot-validation", path = "../../validation" } -polkadot-network = { path = "../../network" } polkadot-primitives = { path = "../../primitives" } polkadot-rpc = { path = "../../rpc" } polkadot-runtime-common = { path = "../../runtime/common" } diff --git a/node/test-service/src/lib.rs b/node/test-service/src/lib.rs index 44b45d8192bf..1f6e45879c78 100644 --- a/node/test-service/src/lib.rs +++ b/node/test-service/src/lib.rs @@ -63,9 +63,7 @@ native_executor_instance!( pub fn polkadot_test_new_full( config: Configuration, collating_for: Option<(CollatorId, ParaId)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, ) -> Result< ( TaskManager, @@ -80,9 +78,7 @@ pub fn polkadot_test_new_full( new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, None, true, )?; @@ -211,7 +207,7 @@ pub fn run_test_node( let multiaddr = config.network.listen_addresses[0].clone(); let authority_discovery_enabled = false; let (task_manager, client, handles, network, rpc_handlers) = - polkadot_test_new_full(config, None, None, authority_discovery_enabled, 6000) + polkadot_test_new_full(config, None, authority_discovery_enabled) .expect("could not create Polkadot test service"); let peer_id = network.local_peer_id().clone(); diff --git a/parachain/test-parachains/adder/collator/Cargo.toml b/parachain/test-parachains/adder/collator/Cargo.toml deleted file mode 100644 index cab8bbf98b1e..000000000000 --- a/parachain/test-parachains/adder/collator/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "test-parachain-adder-collator" -version = "0.1.0" -authors = ["Parity Technologies "] -edition = "2018" - -[dependencies] -adder = { package = "test-parachain-adder", path = ".." } -parachain = { package = "polkadot-parachain", path = "../../.." } -collator = { package = "polkadot-collator", path = "../../../../collator" } -primitives = { package = "polkadot-primitives", path = "../../../../primitives" } -service = { package = "polkadot-service", path = "../../../../service" } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -client-api = { package = "sc-client-api", git = "https://github.com/paritytech/substrate", branch = "master" } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -parking_lot = "0.10.0" -codec = { package = "parity-scale-codec", version = "1.3.4" } -futures = "0.3.4" diff --git a/parachain/test-parachains/adder/collator/src/main.rs b/parachain/test-parachains/adder/collator/src/main.rs deleted file mode 100644 index 318bc6142c88..000000000000 --- a/parachain/test-parachains/adder/collator/src/main.rs +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2018-2020 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 . - -//! Collator for polkadot - -use std::collections::HashMap; -use std::sync::Arc; - -use adder::{HeadData as AdderHead, BlockData as AdderBody}; -use sp_core::{traits::SpawnNamed, Pair}; -use codec::{Encode, Decode}; -use primitives::v0::{ - Block, Hash, DownwardMessage, - HeadData, BlockData, Id as ParaId, LocalValidationData, GlobalValidationData, -}; -use collator::{ParachainContext, Network, BuildParachainContext, Cli, SubstrateCli}; -use parking_lot::Mutex; -use futures::future::{Ready, ready, FutureExt}; -use sp_runtime::traits::BlakeTwo256; -use client_api::Backend as BackendT; - -const GENESIS: AdderHead = AdderHead { - number: 0, - parent_hash: [0; 32], - post_state: [ - 1, 27, 77, 3, 221, 140, 1, 241, 4, 145, 67, 207, 156, 76, 129, 126, 75, - 22, 127, 29, 27, 131, 229, 198, 240, 241, 13, 137, 186, 30, 123, 206 - ], -}; - -const GENESIS_BODY: AdderBody = AdderBody { - state: 0, - add: 0, -}; - -#[derive(Clone)] -struct AdderContext { - db: Arc>>, - /// We store it here to make sure that our interfaces require the correct bounds. - _network: Option>, -} - -/// The parachain context. -impl ParachainContext for AdderContext { - type ProduceCandidate = Ready>; - - fn produce_candidate( - &mut self, - _relay_parent: Hash, - _global_validation: GlobalValidationData, - local_validation: LocalValidationData, - _: Vec, - ) -> Self::ProduceCandidate - { - let adder_head = match AdderHead::decode(&mut &local_validation.parent_head.0[..]).ok() { - Some(res) => res, - None => return ready(None), - }; - - let mut db = self.db.lock(); - - let last_body = if adder_head == GENESIS { - GENESIS_BODY - } else { - db.get(&adder_head) - .expect("All past bodies stored since this is the only collator") - .clone() - }; - - let next_body = AdderBody { - state: last_body.state.overflowing_add(last_body.add).0, - add: adder_head.number % 100, - }; - - let next_head = adder::execute(adder_head.hash(), adder_head, &next_body) - .expect("good execution params; qed"); - - let encoded_head = HeadData(next_head.encode()); - let encoded_body = BlockData(next_body.encode()); - - println!( - "Created collation for #{}, post-state={}", - next_head.number, - next_body.state.overflowing_add(next_body.add).0, - ); - - db.insert(next_head.clone(), next_body); - ready(Some((encoded_body, encoded_head))) - } -} - -impl BuildParachainContext for AdderContext { - type ParachainContext = Self; - - fn build( - self, - _: Arc, - _: SP, - network: impl Network + Clone + 'static, - ) -> Result - where - SP: SpawnNamed + Clone + Send + Sync + 'static, - Backend: BackendT, - Backend::State: sp_api::StateBackend, - Client: service::AbstractClient + 'static, - Client::Api: service::RuntimeApiCollection, - { - Ok(Self { _network: Some(Arc::new(network)), ..self }) - } -} - -fn main() -> Result<(), Box> { - let key = Arc::new(Pair::from_seed(&[1; 32])); - let id: ParaId = 100.into(); - - println!("Starting adder collator with genesis: "); - - { - let encoded = GENESIS.encode(); - println!("Dec: {:?}", encoded); - print!("Hex: 0x"); - for byte in encoded { - print!("{:02x}", byte); - } - - println!(); - } - - let context = AdderContext { - db: Arc::new(Mutex::new(HashMap::new())), - _network: None, - }; - - let cli = Cli::from_iter(&["-dev"]); - let runner = cli.create_runner(&cli.run.base)?; - runner.async_run(|config| { - let (future, task_manager) = collator::start_collator( - context, - id, - key, - config, - )?; - - Ok((future.map(Ok), task_manager)) - })?; - - Ok(()) -} diff --git a/service/Cargo.toml b/service/Cargo.toml index bdc0920eb233..e377aee2a5f9 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -12,13 +12,11 @@ log = "0.4.8" futures = "0.3.4" slog = "2.5.2" hex-literal = "0.2.1" -av_store = { package = "polkadot-availability-store", path = "../availability-store", optional = true } consensus = { package = "polkadot-validation", path = "../validation", optional = true } polkadot-primitives = { path = "../primitives" } polkadot-runtime = { path = "../runtime/polkadot" } kusama-runtime = { path = "../runtime/kusama" } westend-runtime = { path = "../runtime/westend" } -polkadot-network = { path = "../network", optional = true } polkadot-rpc = { path = "../rpc" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } @@ -67,4 +65,4 @@ env_logger = "0.7.0" default = ["db", "full-node"] db = ["service/db"] runtime-benchmarks = ["polkadot-runtime/runtime-benchmarks", "kusama-runtime/runtime-benchmarks", "westend-runtime/runtime-benchmarks"] -full-node = ["av_store", "consensus", "polkadot-network"] +full-node = ["consensus"] diff --git a/service/src/lib.rs b/service/src/lib.rs index 2047b14cb3ef..d2fb3dab00af 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -22,9 +22,7 @@ mod client; use std::sync::Arc; use std::time::Duration; -use polkadot_primitives::v0::{self as parachain, Hash, BlockId}; -#[cfg(feature = "full-node")] -use polkadot_network::{legacy::gossip::Known, protocol as network_protocol}; +use polkadot_primitives::v0 as parachain; use service::error::Error as ServiceError; use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider}; use sc_executor::native_executor_instance; @@ -249,9 +247,7 @@ pub fn new_partial(config: &mut Configuration, test: bool) pub fn new_full( mut config: Configuration, collating_for: Option<(CollatorId, parachain::Id)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, grandpa_pause: Option<(u32, u32)>, test: bool, ) -> Result<( @@ -275,10 +271,6 @@ pub fn new_full( let role = config.role.clone(); let is_authority = role.is_authority() && !is_collator; let force_authoring = config.force_authoring; - let db_path = match config.database.path() { - Some(path) => std::path::PathBuf::from(path), - None => return Err("Starting a Polkadot service with a custom database isn't supported".to_string().into()), - }; let disable_grandpa = config.disable_grandpa; let name = config.network.node_name.clone(); @@ -333,107 +325,16 @@ pub fn new_full( let shared_voter_state = rpc_setup; - let known_oracle = client.clone(); - - let mut handles = FullNodeHandles::default(); - let gossip_validator_select_chain = select_chain.clone(); - - let is_known = move |block_hash: &Hash| { - use consensus_common::BlockStatus; - - match known_oracle.block_status(&BlockId::hash(*block_hash)) { - Err(_) | Ok(BlockStatus::Unknown) | Ok(BlockStatus::Queued) => None, - Ok(BlockStatus::KnownBad) => Some(Known::Bad), - Ok(BlockStatus::InChainWithState) | Ok(BlockStatus::InChainPruned) => { - match gossip_validator_select_chain.leaves() { - Err(_) => None, - Ok(leaves) => if leaves.contains(block_hash) { - Some(Known::Leaf) - } else { - Some(Known::Old) - }, - } - } - } - }; - - let polkadot_network_service = network_protocol::start( - network.clone(), - network_protocol::Config { - collating_for, - }, - (is_known, client.clone()), - client.clone(), - task_manager.spawn_handle(), - ).map_err(|e| format!("Could not spawn network worker: {:?}", e))?; - - let authority_handles = if is_collator || role.is_authority() { - let availability_store = { - use std::path::PathBuf; - - let mut path = PathBuf::from(db_path); - path.push("availability"); - - #[cfg(not(target_os = "unknown"))] - { - av_store::Store::new( - ::av_store::Config { - cache_size: None, - path, - }, - polkadot_network_service.clone(), - )? - } - - #[cfg(target_os = "unknown")] - av_store::Store::new_in_memory(gossip) - }; - - polkadot_network_service.register_availability_store(availability_store.clone()); - - let (validation_service_handle, validation_service) = consensus::ServiceBuilder { - client: client.clone(), - network: polkadot_network_service.clone(), - collators: polkadot_network_service.clone(), - spawner: task_manager.spawn_handle(), - availability_store: availability_store.clone(), - select_chain: select_chain.clone(), - keystore: keystore.clone(), - max_block_data_size, - }.build(); - - task_manager.spawn_essential_handle().spawn("validation-service", Box::pin(validation_service)); - - handles.validation_service_handle = Some(validation_service_handle.clone()); - - Some((validation_service_handle, availability_store)) - } else { - None - }; - if role.is_authority() { - let (validation_service_handle, availability_store) = authority_handles - .clone() - .expect("Authority handles are set for authority nodes; qed"); - let proposer = consensus::ProposerFactory::new( client.clone(), transaction_pool, - validation_service_handle, - slot_duration, prometheus_registry.as_ref(), ); let can_author_with = consensus_common::CanAuthorWithNativeVersion::new(client.executor().clone()); - let block_import = availability_store.block_import( - block_import, - client.clone(), - task_manager.spawn_handle(), - keystore.clone(), - )?; - let babe_config = babe::BabeParams { keystore: keystore.clone(), client: client.clone(), @@ -556,8 +457,7 @@ pub fn new_full( network_starter.start_network(); - handles.polkadot_network = Some(polkadot_network_service); - Ok((task_manager, client, handles, network, rpc_handlers)) + Ok((task_manager, client, FullNodeHandles, network, rpc_handlers)) } /// Builds a new service for a light client. @@ -693,9 +593,7 @@ where pub fn polkadot_new_full( config: Configuration, collating_for: Option<(CollatorId, parachain::Id)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, grandpa_pause: Option<(u32, u32)>, ) -> Result<( @@ -707,9 +605,7 @@ pub fn polkadot_new_full( let (service, client, handles, _, _) = new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, )?; @@ -722,9 +618,7 @@ pub fn polkadot_new_full( pub fn kusama_new_full( config: Configuration, collating_for: Option<(CollatorId, parachain::Id)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, grandpa_pause: Option<(u32, u32)>, ) -> Result<( TaskManager, @@ -735,9 +629,7 @@ pub fn kusama_new_full( let (service, client, handles, _, _) = new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, )?; @@ -750,9 +642,7 @@ pub fn kusama_new_full( pub fn westend_new_full( config: Configuration, collating_for: Option<(CollatorId, parachain::Id)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, grandpa_pause: Option<(u32, u32)>, ) -> Result<( @@ -764,9 +654,7 @@ pub fn westend_new_full( let (service, client, handles, _, _) = new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, )?; @@ -778,12 +666,7 @@ pub fn westend_new_full( /// of the node may use. #[cfg(feature = "full-node")] #[derive(Default)] -pub struct FullNodeHandles { - /// A handle to the Polkadot networking protocol. - pub polkadot_network: Option, - /// A handle to the validation service. - pub validation_service_handle: Option, -} +pub struct FullNodeHandles; /// Build a new light node. pub fn build_light(config: Configuration) -> Result<(TaskManager, RpcHandlers), ServiceError> { @@ -801,18 +684,14 @@ pub fn build_light(config: Configuration) -> Result<(TaskManager, RpcHandlers), pub fn build_full( config: Configuration, collating_for: Option<(CollatorId, parachain::Id)>, - max_block_data_size: Option, authority_discovery_enabled: bool, - slot_duration: u64, grandpa_pause: Option<(u32, u32)>, ) -> Result<(TaskManager, Client, FullNodeHandles), ServiceError> { if config.chain_spec.is_kusama() { new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, ).map(|(task_manager, client, handles, _, _)| (task_manager, Client::Kusama(client), handles)) @@ -820,9 +699,7 @@ pub fn build_full( new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, ).map(|(task_manager, client, handles, _, _)| (task_manager, Client::Westend(client), handles)) @@ -830,9 +707,7 @@ pub fn build_full( new_full::( config, collating_for, - max_block_data_size, authority_discovery_enabled, - slot_duration, grandpa_pause, false, ).map(|(task_manager, client, handles, _, _)| (task_manager, Client::Polkadot(client), handles)) diff --git a/validation/Cargo.toml b/validation/Cargo.toml index 2c345b0c6260..a9ce31214d09 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -5,37 +5,27 @@ authors = ["Parity Technologies "] edition = "2018" [dependencies] -ansi_term = "0.12.1" +polkadot-primitives = { path = "../primitives" } +parachain = { package = "polkadot-parachain", path = "../parachain" } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } +sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } +consensus = { package = "sp-consensus", git = "https://github.com/paritytech/substrate", branch = "master" } +runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master" } futures = "0.3.4" -futures-timer = "2.0" -parking_lot = "0.9.0" -tokio = { version = "0.2.13", features = ["rt-core", "blocking"] } -derive_more = "0.14.1" log = "0.4.8" -exit-future = "0.2.0" +derive_more = "0.14.1" codec = { package = "parity-scale-codec", version = "1.3.4", default-features = false, features = ["derive"] } -availability_store = { package = "polkadot-availability-store", path = "../availability-store" } -parachain = { package = "polkadot-parachain", path = "../parachain" } -polkadot-primitives = { path = "../primitives" } -polkadot-erasure-coding = { path = "../erasure-coding" } -table = { package = "polkadot-statement-table", path = "../statement-table" } grandpa = { package = "sc-finality-grandpa", git = "https://github.com/paritytech/substrate", branch = "master" } inherents = { package = "sp-inherents", git = "https://github.com/paritytech/substrate", branch = "master" } -consensus = { package = "sp-consensus", git = "https://github.com/paritytech/substrate", branch = "master" } primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "master" } txpool-api = { package = "sp-transaction-pool", git = "https://github.com/paritytech/substrate", branch = "master" } -sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-timestamp = { git = "https://github.com/paritytech/substrate", branch = "master" } block-builder = { package = "sc-block-builder", git = "https://github.com/paritytech/substrate", branch = "master" } trie = { package = "sp-trie", git = "https://github.com/paritytech/substrate", branch = "master" } -runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master" } -bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] } babe-primitives = { package = "sp-consensus-babe", git = "https://github.com/paritytech/substrate", branch = "master" } -keystore = { package = "sc-keystore", git = "https://github.com/paritytech/substrate", branch = "master" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", git = "https://github.com/paritytech/substrate", branch = "master" } -sc-basic-authorship = { git = "https://github.com/paritytech/substrate", branch = "master" } [dev-dependencies] sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/validation/src/block_production.rs b/validation/src/block_production.rs index 4804ba230c96..047a12a457bc 100644 --- a/validation/src/block_production.rs +++ b/validation/src/block_production.rs @@ -22,36 +22,25 @@ use std::{ pin::Pin, sync::Arc, - time::{self, Duration, Instant}, + time::Duration, }; use sp_blockchain::HeaderBackend; use block_builder::{BlockBuilderApi, BlockBuilderProvider}; use consensus::{Proposal, RecordProof}; -use polkadot_primitives::v0::{Block, Header}; -use polkadot_primitives::v0::{ - ParachainHost, NEW_HEADS_IDENTIFIER, -}; +use polkadot_primitives::v0::{NEW_HEADS_IDENTIFIER, Block, Header, AttestedCandidate}; use runtime_primitives::traits::{DigestFor, HashFor}; -use futures_timer::Delay; use txpool_api::TransactionPool; use futures::prelude::*; use inherents::InherentData; -use sp_timestamp::TimestampInherentData; use sp_api::{ApiExt, ProvideRuntimeApi}; use prometheus_endpoint::Registry as PrometheusRegistry; -use crate::{ - Error, - dynamic_inclusion::DynamicInclusion, - validation_service::ServiceHandle, -}; +use crate::Error; // Polkadot proposer factory. pub struct ProposerFactory { - service_handle: ServiceHandle, - babe_slot_duration: u64, factory: sc_basic_authorship::ProposerFactory, } @@ -60,8 +49,6 @@ impl ProposerFactory { pub fn new( client: Arc, transaction_pool: Arc, - service_handle: ServiceHandle, - babe_slot_duration: u64, prometheus: Option<&PrometheusRegistry>, ) -> Self { let factory = sc_basic_authorship::ProposerFactory::new( @@ -70,8 +57,6 @@ impl ProposerFactory { prometheus, ); ProposerFactory { - service_handle, - babe_slot_duration, factory, } } @@ -82,7 +67,7 @@ impl consensus::Environment where TxPool: TransactionPool + 'static, Client: BlockBuilderProvider + ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, - Client::Api: ParachainHost + BlockBuilderApi + Client::Api: BlockBuilderApi + ApiExt, Backend: sc_client_api::Backend< Block, @@ -101,37 +86,24 @@ where &mut self, parent_header: &Header, ) -> Self::CreateProposer { - let parent_hash = parent_header.hash(); - let slot_duration = self.babe_slot_duration.clone(); - let proposer = self.factory.init(parent_header).into_inner(); - - let maybe_proposer = self.service_handle - .clone() - .get_validation_instance(parent_hash) - .and_then(move |tracker| future::ready(proposer - .map_err(Into::into) - .map(|proposer| Proposer { - tracker, - slot_duration, - proposer, - }) - )); - - Box::pin(maybe_proposer) + let proposer = self.factory.init(parent_header) + .into_inner() + .map_err(Into::into) + .map(|proposer| Proposer { proposer }); + + Box::pin(future::ready(proposer)) } } /// The Polkadot proposer logic. pub struct Proposer, Backend> { - tracker: crate::validation_service::ValidationInstanceHandle, - slot_duration: u64, proposer: sc_basic_authorship::Proposer, } impl consensus::Proposer for Proposer where TxPool: TransactionPool + 'static, Client: BlockBuilderProvider + ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, - Client::Api: ParachainHost + BlockBuilderApi + ApiExt, + Client::Api: BlockBuilderApi + ApiExt, Backend: sc_client_api::Backend> + 'static, // Rust bug: https://github.com/rust-lang/rust/issues/24159 sp_api::StateBackendFor: sp_api::StateBackend> + Send, @@ -140,8 +112,11 @@ impl consensus::Proposer for Proposer; type Proposal = Pin< Box< - dyn Future>, Error>> - + Send + dyn Future>, + Self::Error, + >> + + Send > >; @@ -152,57 +127,17 @@ impl consensus::Proposer for Proposer Self::Proposal { - const SLOT_DURATION_DENOMINATOR: u64 = 3; // wait up to 1/3 of the slot for candidates. - - let initial_included = self.tracker.table().includable_count(); - let now = Instant::now(); - - let dynamic_inclusion = DynamicInclusion::new( - self.tracker.table().num_parachains(), - self.tracker.started(), - Duration::from_millis(self.slot_duration / SLOT_DURATION_DENOMINATOR), - ); - async move { - let enough_candidates = dynamic_inclusion.acceptable_in( - now, - initial_included, - ).unwrap_or_else(|| Duration::from_millis(1)); - - let believed_timestamp = match inherent_data.timestamp_inherent_data() { - Ok(timestamp) => timestamp, - Err(e) => return Err(Error::InherentError(e)), - }; - - let deadline_diff = max_duration - max_duration / 3; - - // set up delay until next allowed timestamp. - let current_timestamp = current_timestamp(); - if current_timestamp < believed_timestamp { - Delay::new(Duration::from_millis(current_timestamp - believed_timestamp)) - .await; - } - - Delay::new(enough_candidates).await; - - let proposed_candidates = self.tracker.table().proposed_set(); - let mut inherent_data = inherent_data; - inherent_data.put_data(NEW_HEADS_IDENTIFIER, &proposed_candidates) + inherent_data.put_data(NEW_HEADS_IDENTIFIER, &Vec::::new()) .map_err(Error::InherentError)?; self.proposer.propose( inherent_data, inherent_digests.clone(), - deadline_diff, + max_duration, record_proof ).await.map_err(Into::into) }.boxed() } } - -fn current_timestamp() -> u64 { - time::SystemTime::now().duration_since(time::UNIX_EPOCH) - .expect("now always later than unix epoch; qed") - .as_millis() as u64 -} diff --git a/validation/src/collation.rs b/validation/src/collation.rs deleted file mode 100644 index 53e18ab3abb5..000000000000 --- a/validation/src/collation.rs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Validator-side view of collation. -//! -//! This module contains type definitions, a trait for a batch of collators, and a trait for -//! attempting to fetch a collation repeatedly until a valid one is obtained. - -use std::sync::Arc; - -use polkadot_primitives::v0::{ - BlakeTwo256, Block, Hash, HashT, - CollatorId, ParachainHost, Id as ParaId, Collation, ErasureChunk, CollationInfo, -}; -use polkadot_erasure_coding as erasure; -use sp_api::ProvideRuntimeApi; -use futures::prelude::*; -use log::debug; -use primitives::traits::SpawnNamed; - -/// Encapsulates connections to collators and allows collation on any parachain. -/// -/// This is expected to be a lightweight, shared type like an `Arc`. -pub trait Collators: Clone { - /// Errors when producing collations. - type Error: std::fmt::Debug; - /// A full collation. - type Collation: Future>; - - /// Collate on a specific parachain, building on a given relay chain parent hash. - /// - /// The returned collation should be checked for basic validity in the signature - /// and will be checked for state-transition validity by the consumer of this trait. - /// - /// This does not have to guarantee local availability, as a valid collation - /// will be passed to the `TableRouter` instance. - /// - /// The returned future may be prematurely concluded if the `relay_parent` goes - /// out of date. - fn collate(&self, parachain: ParaId, relay_parent: Hash) -> Self::Collation; - - /// Note a bad collator. TODO: take proof (https://github.com/paritytech/polkadot/issues/217) - fn note_bad_collator(&self, collator: CollatorId); -} - -/// A future which resolves when a collation is available. -pub async fn collation_fetch( - validation_pool: Option, - parachain: ParaId, - relay_parent: Hash, - collators: C, - client: Arc

, - max_block_data_size: Option, - n_validators: usize, - spawner: impl SpawnNamed + Clone + 'static, -) -> Result<(CollationInfo, crate::pipeline::FullOutput), C::Error> - where - P::Api: ParachainHost, - C: Collators + Unpin, - P: ProvideRuntimeApi, - ::Collation: Unpin, -{ - loop { - let collation = collators.collate(parachain, relay_parent).await?; - let Collation { info, pov } = collation; - let res = crate::pipeline::full_output_validation_with_api( - validation_pool.as_ref(), - &*client, - &info, - &pov, - &relay_parent, - max_block_data_size, - n_validators, - spawner.clone(), - ); - - match res { - Ok(full_output) => { - return Ok((info, full_output)) - } - Err(e) => { - debug!("Failed to validate parachain due to API error: {}", e); - - // just continue if we got a bad collation or failed to validate - collators.note_bad_collator(info.collator) - } - } - } -} - -/// Validate an erasure chunk against an expected root. -pub fn validate_chunk( - root: &Hash, - chunk: &ErasureChunk, -) -> Result<(), ()> { - let expected = erasure::branch_hash(root, &chunk.proof, chunk.index as usize).map_err(|_| ())?; - let got = BlakeTwo256::hash(&chunk.chunk); - - if expected != got { - return Err(()) - } - - Ok(()) -} diff --git a/validation/src/dynamic_inclusion.rs b/validation/src/dynamic_inclusion.rs deleted file mode 100644 index d1091159d6a7..000000000000 --- a/validation/src/dynamic_inclusion.rs +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Dynamic inclusion threshold over time. - -use std::time::{Duration, Instant}; - -fn duration_to_micros(duration: &Duration) -> u64 { - duration.as_secs() * 1_000_000 + (duration.subsec_nanos() / 1000) as u64 -} - -/// Dynamic inclusion threshold over time. -/// -/// The acceptable proportion of parachains which must have parachain candidates -/// reduces over time (eventually going to zero). -#[derive(Debug, Clone)] -pub struct DynamicInclusion { - start: Instant, - y: u64, - m: u64, -} - -impl DynamicInclusion { - /// Constructs a new dynamic inclusion threshold calculator based on the time now, - /// how many parachain candidates are required at the beginning, and when an empty - /// block will be allowed. - pub fn new(initial: usize, start: Instant, allow_empty: Duration) -> Self { - // linear function f(n_candidates) -> valid after microseconds - // f(0) = allow_empty - // f(initial) = 0 - // m is actually the negative slope to avoid using signed arithmetic. - let (y, m) = if initial != 0 { - let y = duration_to_micros(&allow_empty); - - (y, y / initial as u64) - } else { - (0, 0) - }; - - DynamicInclusion { - start, - y, - m, - } - } - - /// Returns the duration from `now` after which the amount of included parachain candidates - /// would be enough, or `None` if it is sufficient now. - /// - /// Panics if `now` is earlier than the `start`. - pub fn acceptable_in(&self, now: Instant, included: usize) -> Option { - let elapsed = now.duration_since(self.start); - let elapsed = duration_to_micros(&elapsed); - - let valid_after = self.y.saturating_sub(self.m * included as u64); - - if elapsed >= valid_after { - None - } else { - Some(Duration::from_millis((valid_after - elapsed) as u64 / 1000)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn full_immediately_allowed() { - let now = Instant::now(); - - let dynamic = DynamicInclusion::new( - 10, - now, - Duration::from_millis(4000), - ); - - assert!(dynamic.acceptable_in(now, 10).is_none()); - assert!(dynamic.acceptable_in(now, 11).is_none()); - assert!(dynamic.acceptable_in(now + Duration::from_millis(2000), 10).is_none()); - } - - #[test] - fn half_allowed_halfway() { - let now = Instant::now(); - - let dynamic = DynamicInclusion::new( - 10, - now, - Duration::from_millis(4000), - ); - - assert_eq!(dynamic.acceptable_in(now, 5), Some(Duration::from_millis(2000))); - assert!(dynamic.acceptable_in(now + Duration::from_millis(2000), 5).is_none()); - assert!(dynamic.acceptable_in(now + Duration::from_millis(3000), 5).is_none()); - assert!(dynamic.acceptable_in(now + Duration::from_millis(4000), 5).is_none()); - } - - #[test] - fn zero_initial_is_flat() { - let now = Instant::now(); - - let dynamic = DynamicInclusion::new( - 0, - now, - Duration::from_secs(10_000), - ); - - for i in 0..10_001 { - let now = now + Duration::from_secs(i); - assert!(dynamic.acceptable_in(now, 0).is_none()); - assert!(dynamic.acceptable_in(now, 1).is_none()); - assert!(dynamic.acceptable_in(now, 10).is_none()); - } - } -} diff --git a/validation/src/error.rs b/validation/src/error.rs index d4632a4782fb..913b110e7f67 100644 --- a/validation/src/error.rs +++ b/validation/src/error.rs @@ -16,8 +16,6 @@ //! Errors that can occur during the validation process. -use polkadot_primitives::v0::{ValidatorId, Hash}; - /// Error type for validation #[derive(Debug, derive_more::Display, derive_more::From)] pub enum Error { @@ -25,66 +23,9 @@ pub enum Error { Client(sp_blockchain::Error), /// Consensus error Consensus(consensus::error::Error), - /// A wasm-validation error. - WasmValidation(parachain::wasm_executor::ValidationError), - /// An I/O error. - Io(std::io::Error), - /// An error in the availability erasure-coding. - ErasureCoding(polkadot_erasure_coding::Error), - #[display(fmt = "Invalid duty roster length: expected {}, got {}", expected, got)] - InvalidDutyRosterLength { - /// Expected roster length - expected: usize, - /// Actual roster length - got: usize, - }, - /// Local account not a validator at this block - #[display(fmt = "Local account ID ({:?}) not a validator at this block.", _0)] - NotValidator(ValidatorId), /// Unexpected error checking inherents #[display(fmt = "Unexpected error while checking inherents: {}", _0)] InherentError(inherents::Error), - /// Proposer destroyed before finishing proposing or evaluating - #[display(fmt = "Proposer destroyed before finishing proposing or evaluating")] - PrematureDestruction, - /// Failed to build the table router. - #[display(fmt = "Failed to build the table router: {}", _0)] - CouldNotBuildTableRouter(String), - /// Timer failed - #[display(fmt = "Timer failed: {}", _0)] - Timer(std::io::Error), - #[display(fmt = "Failed to compute deadline of now + {:?}", _0)] - DeadlineComputeFailure(std::time::Duration), - #[display(fmt = "Validation service is down.")] - ValidationServiceDown, - /// PoV-block in collation doesn't match provided. - #[display(fmt = "PoV hash mismatch. Expected {:?}, got {:?}", _0, _1)] - PoVHashMismatch(Hash, Hash), - /// Collator signature is invalid. - #[display(fmt = "Invalid collator signature on collation")] - InvalidCollatorSignature, - /// Head-data too large. - #[display(fmt = "Head data size of {} exceeded maximum of {}", _0, _1)] - HeadDataTooLarge(usize, usize), - /// Head-data mismatch after validation. - #[display(fmt = "Validation produced a different parachain header")] - HeadDataMismatch, - /// Relay parent of candidate not allowed. - #[display(fmt = "Relay parent {} of candidate not allowed in this context.", _0)] - DisallowedRelayParent(Hash), - /// Commitments in candidate match commitments produced by validation. - #[display(fmt = "Commitments in candidate receipt do not match those produced by validation")] - CommitmentsMismatch, - /// The parachain for which validation work is being done is not active. - #[display(fmt = "Parachain {:?} is not active", _0)] - InactiveParachain(polkadot_primitives::v0::Id), - /// Block data is too big - #[display(fmt = "Block data is too big (maximum allowed size: {}, actual size: {})", size, max_size)] - BlockDataTooBig { size: u64, max_size: u64 }, - Join(tokio::task::JoinError), - /// Could not cover fee for an operation e.g. for sending `UpwardMessage`. - #[display(fmt = "Parachain could not cover fee for an operation e.g. for sending an `UpwardMessage`.")] - CouldNotCoverFee, } impl std::error::Error for Error { @@ -92,9 +33,6 @@ impl std::error::Error for Error { match self { Error::Client(ref err) => Some(err), Error::Consensus(ref err) => Some(err), - Error::WasmValidation(ref err) => Some(err), - Error::ErasureCoding(ref err) => Some(err), - Error::Io(ref err) => Some(err), _ => None, } } diff --git a/validation/src/lib.rs b/validation/src/lib.rs index a5b1c1d5c2df..ff86cdd11d91 100644 --- a/validation/src/lib.rs +++ b/validation/src/lib.rs @@ -29,207 +29,10 @@ //! //! Groups themselves may be compromised by malicious authorities. -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; -use codec::Encode; -use polkadot_primitives::v0::{ - Id as ParaId, Chain, DutyRoster, AbridgedCandidateReceipt, - CompactStatement as PrimitiveStatement, - PoVBlock, ErasureChunk, ValidatorSignature, ValidatorIndex, - ValidatorPair, ValidatorId, SigningContext, -}; -use primitives::Pair; - -use futures::prelude::*; - pub use self::block_production::ProposerFactory; -pub use self::collation::Collators; pub use self::error::Error; -pub use self::shared_table::{ - SharedTable, ParachainWork, PrimedParachainWork, Validated, Statement, SignedStatement, - GenericStatement, -}; -pub use self::validation_service::{ServiceHandle, ServiceBuilder}; - pub use parachain::wasm_executor::run_worker as run_validation_worker; -mod dynamic_inclusion; mod error; -mod shared_table; pub mod block_production; -pub mod collation; -pub mod pipeline; -pub mod validation_service; - -/// A handle to a statement table router. -/// -/// This is expected to be a lightweight, shared type like an `Arc`. -/// Once all instances are dropped, consensus networking for this router -/// should be cleaned up. -pub trait TableRouter: Clone { - /// Errors when fetching data from the network. - type Error: std::fmt::Debug; - /// Future that drives sending of the local collation to the network. - type SendLocalCollation: Future>; - /// Future that resolves when candidate data is fetched. - type FetchValidationProof: Future>; - - /// Call with local candidate data. This will sign, import, and broadcast a statement about the candidate. - fn local_collation( - &self, - receipt: AbridgedCandidateReceipt, - pov_block: PoVBlock, - chunks: (ValidatorIndex, &[ErasureChunk]), - ) -> Self::SendLocalCollation; - - /// Fetch validation proof for a specific candidate. - /// - /// This future must conclude once all `Clone`s of this `TableRouter` have - /// been cleaned up. - fn fetch_pov_block(&self, candidate: &AbridgedCandidateReceipt) -> Self::FetchValidationProof; -} - -/// A long-lived network which can create parachain statement and BFT message routing processes on demand. -pub trait Network { - /// The error type of asynchronously building the table router. - type Error: std::fmt::Debug; - - /// The table router type. This should handle importing of any statements, - /// routing statements to peers, and driving completion of any `StatementProducers`. - type TableRouter: TableRouter; - - /// The future used for asynchronously building the table router. - /// This should not fail. - type BuildTableRouter: Future>; - - /// Instantiate a table router using the given shared table. - /// Also pass through any outgoing messages to be broadcast to peers. - #[must_use] - fn build_table_router( - &self, - table: Arc, - authorities: &[ValidatorId], - ) -> Self::BuildTableRouter; -} - -/// The local duty of a validator. -#[derive(Debug)] -pub struct LocalDuty { - validation: Chain, - index: ValidatorIndex, -} - -/// Information about a specific group. -#[derive(Debug, Clone, Default)] -pub struct GroupInfo { - /// Authorities meant to check validity of candidates. - validity_guarantors: HashSet, - /// Number of votes needed for validity. - needed_validity: usize, -} - -/// Sign a table statement against a parent hash. -/// The actual message signed is the encoded statement concatenated with the -/// parent hash. -pub fn sign_table_statement( - statement: &Statement, - key: &ValidatorPair, - signing_context: &SigningContext, -) -> ValidatorSignature { - let mut encoded = PrimitiveStatement::from(statement).encode(); - encoded.extend(signing_context.encode()); - - key.sign(&encoded) -} - -/// Check signature on table statement. -pub fn check_statement( - statement: &Statement, - signature: &ValidatorSignature, - signer: ValidatorId, - signing_context: &SigningContext, -) -> bool { - use runtime_primitives::traits::AppVerify; - - let mut encoded = PrimitiveStatement::from(statement).encode(); - encoded.extend(signing_context.encode()); - - signature.verify(&encoded[..], &signer) -} - -/// Compute group info out of a duty roster and a local authority set. -pub fn make_group_info( - roster: DutyRoster, - authorities: &[ValidatorId], - local_id: Option, -) -> Result<(HashMap, Option), Error> { - if roster.validator_duty.len() != authorities.len() { - return Err(Error::InvalidDutyRosterLength { - expected: authorities.len(), - got: roster.validator_duty.len() - }); - } - - let mut local_validation = None; - let mut local_index = 0; - let mut map = HashMap::new(); - - let duty_iter = authorities.iter().zip(&roster.validator_duty); - for (i, (authority, v_duty)) in duty_iter.enumerate() { - if Some(authority) == local_id.as_ref() { - local_validation = Some(v_duty.clone()); - local_index = i; - } - - match *v_duty { - Chain::Relay => {}, // does nothing for now. - Chain::Parachain(ref id) => { - map.entry(id.clone()).or_insert_with(GroupInfo::default) - .validity_guarantors - .insert(authority.clone()); - } - } - } - - for live_group in map.values_mut() { - let validity_len = live_group.validity_guarantors.len(); - live_group.needed_validity = validity_len / 2 + validity_len % 2; - } - - - let local_duty = local_validation.map(|v| LocalDuty { - validation: v, - index: local_index as u32, - }); - - Ok((map, local_duty)) -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_keyring::Sr25519Keyring; - - #[test] - fn sign_and_check_statement() { - let statement: Statement = GenericStatement::Valid([1; 32].into()); - let parent_hash = [2; 32].into(); - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash, - }; - - let sig = sign_table_statement(&statement, &Sr25519Keyring::Alice.pair().into(), &signing_context); - - let wrong_signing_context = SigningContext { - session_index: Default::default(), - parent_hash: [0xff; 32].into(), - }; - assert!(check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &signing_context)); - assert!(!check_statement(&statement, &sig, Sr25519Keyring::Alice.public().into(), &wrong_signing_context)); - assert!(!check_statement(&statement, &sig, Sr25519Keyring::Bob.public().into(), &signing_context)); - } -} diff --git a/validation/src/pipeline.rs b/validation/src/pipeline.rs deleted file mode 100644 index 7fe18212f081..000000000000 --- a/validation/src/pipeline.rs +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright 2020 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 . - -//! The pipeline of validation functions a parachain block must pass through before -//! it can be voted for. - -use codec::Encode; -use polkadot_erasure_coding as erasure; -use polkadot_primitives::v0::{ - CollationInfo, PoVBlock, LocalValidationData, GlobalValidationData, OmittedValidationData, - AvailableData, FeeSchedule, CandidateCommitments, ErasureChunk, ParachainHost, - Id as ParaId, AbridgedCandidateReceipt, ValidationCode, -}; -use polkadot_primitives::v0::{Block, BlockId, Balance, Hash}; -use parachain::{ - wasm_executor::{self, ExecutionMode}, - primitives::{UpwardMessage, ValidationParams}, -}; -use runtime_primitives::traits::{BlakeTwo256, Hash as HashT}; -use sp_api::ProvideRuntimeApi; -use crate::Error; -use primitives::traits::SpawnNamed; - -pub use parachain::wasm_executor::ValidationPool; - -/// Does basic checks of a collation. Provide the encoded PoV-block. -pub fn basic_checks( - collation: &CollationInfo, - expected_relay_parent: &Hash, - max_block_data_size: Option, - encoded_pov: &[u8], -) -> Result<(), Error> { - if &collation.relay_parent != expected_relay_parent { - return Err(Error::DisallowedRelayParent(collation.relay_parent)); - } - - if let Some(max_size) = max_block_data_size { - if encoded_pov.len() as u64 > max_size { - return Err(Error::BlockDataTooBig { size: encoded_pov.len() as _, max_size }); - } - } - - let hash = BlakeTwo256::hash(encoded_pov); - if hash != collation.pov_block_hash { - return Err(Error::PoVHashMismatch(collation.pov_block_hash, hash)); - } - - if let Err(()) = collation.check_signature() { - return Err(Error::InvalidCollatorSignature); - } - - Ok(()) -} - -/// Data from a fully-outputted validation of a parachain candidate. This contains -/// all outputs and commitments of the validation as well as all additional data to make available. -pub struct FullOutput { - /// Data about the candidate to keep available in the network. - pub available_data: AvailableData, - /// Commitments issued alongside the candidate to be placed on-chain. - pub commitments: CandidateCommitments, - /// All erasure-chunks associated with the available data. Each validator - /// should keep their chunk (by index). Other chunks do not need to be - /// kept available long-term, but should be distributed to other validators. - pub erasure_chunks: Vec, - /// The number of validators that were present at this validation. - pub n_validators: usize, -} - -impl FullOutput { - /// Check consistency of the outputs produced by the validation pipeline against - /// data contained within a candidate receipt. - pub fn check_consistency(&self, receipt: &AbridgedCandidateReceipt) -> Result<(), Error> { - if self.commitments != receipt.commitments { - Err(Error::CommitmentsMismatch) - } else { - Ok(()) - } - } -} - -/// The successful result of validating a collation. If the full commitments of the -/// validation are needed, call `full_output`. Otherwise, safely drop this value. -pub struct ValidatedCandidate<'a> { - pov_block: &'a PoVBlock, - global_validation: &'a GlobalValidationData, - local_validation: &'a LocalValidationData, - upward_messages: Vec, - fees: Balance, - processed_downward_messages: u32, -} - -impl<'a> ValidatedCandidate<'a> { - /// Fully-compute the commitments and outputs of the candidate. Provide the number - /// of validators. This computes the erasure-coding. - pub fn full_output(self, n_validators: usize) -> Result { - let ValidatedCandidate { - pov_block, - global_validation, - local_validation, - upward_messages, - fees, - processed_downward_messages, - } = self; - - let omitted_validation = OmittedValidationData { - global_validation: global_validation.clone(), - local_validation: local_validation.clone(), - }; - - let available_data = AvailableData { - pov_block: pov_block.clone(), - omitted_validation, - }; - - let erasure_chunks = erasure::obtain_chunks_v0( - n_validators, - &available_data, - )?; - - let branches = erasure::branches(erasure_chunks.as_ref()); - let erasure_root = branches.root(); - - let chunks: Vec<_> = erasure_chunks - .iter() - .zip(branches.map(|(proof, _)| proof)) - .enumerate() - .map(|(index, (chunk, proof))| ErasureChunk { - // branches borrows the original chunks, but this clone could probably be dodged. - chunk: chunk.clone(), - index: index as u32, - proof, - }) - .collect(); - - let commitments = CandidateCommitments { - upward_messages, - fees, - erasure_root, - new_validation_code: None, - processed_downward_messages, - }; - - Ok(FullOutput { - available_data, - commitments, - erasure_chunks: chunks, - n_validators, - }) - } -} - -/// Validate that the given `UpwardMessage`s are covered by the given `free_balance`. -/// -/// Will return an error if the `free_balance` does not cover the required fees to the -/// given `msgs`. On success it returns the fees that need to be charged for the `msgs`. -fn validate_upward_messages( - msgs: &[UpwardMessage], - fee_schedule: FeeSchedule, - free_balance: Balance, -) -> Result { - msgs.iter().try_fold(Balance::from(0u128), |fees_charged, msg| { - let fees = fee_schedule.compute_message_fee(msg.data.len()); - let fees_charged = fees_charged.saturating_add(fees); - - if fees_charged > free_balance { - Err(Error::CouldNotCoverFee) - } else { - Ok(fees_charged) - } - }) -} - -/// Does full checks of a collation, with provided PoV-block and contextual data. -pub fn validate<'a>( - validation_pool: Option<&'_ ValidationPool>, - collation: &'a CollationInfo, - pov_block: &'a PoVBlock, - local_validation: &'a LocalValidationData, - global_validation: &'a GlobalValidationData, - validation_code: &ValidationCode, - spawner: impl SpawnNamed + 'static, -) -> Result, Error> { - if collation.head_data.0.len() > global_validation.max_head_data_size as _ { - return Err(Error::HeadDataTooLarge( - collation.head_data.0.len(), - global_validation.max_head_data_size as _, - )); - } - - let params = ValidationParams { - parent_head: local_validation.parent_head.clone(), - block_data: pov_block.block_data.clone(), - relay_chain_height: global_validation.block_number, - hrmp_mqc_heads: Vec::new(), - }; - - // TODO: remove when ext does not do this. - let fee_schedule = FeeSchedule { - base: 0, - per_byte: 0, - }; - - let execution_mode = validation_pool - .map(ExecutionMode::Remote) - .unwrap_or(ExecutionMode::Local); - - match wasm_executor::validate_candidate( - &validation_code.0, - params, - execution_mode, - spawner, - ) { - Ok(result) => { - if result.head_data == collation.head_data { - let fees = validate_upward_messages( - &result.upward_messages, - fee_schedule, - local_validation.balance, - )?; - - Ok(ValidatedCandidate { - pov_block, - global_validation, - local_validation, - upward_messages: result.upward_messages, - fees, - processed_downward_messages: result.processed_downward_messages, - }) - } else { - Err(Error::HeadDataMismatch) - } - } - Err(e) => Err(e.into()), - } -} - -/// Extracts validation parameters from a Polkadot runtime API for a specific parachain. -pub fn validation_params

(api: &P, relay_parent: Hash, para_id: ParaId) - -> Result<(LocalValidationData, GlobalValidationData, ValidationCode), Error> -where - P: ProvideRuntimeApi, - P::Api: ParachainHost, -{ - let api = api.runtime_api(); - let relay_parent = BlockId::hash(relay_parent); - - // fetch all necessary data from runtime. - let local_validation = api.local_validation_data(&relay_parent, para_id)? - .ok_or_else(|| Error::InactiveParachain(para_id))?; - - let global_validation = api.global_validation_data(&relay_parent)?; - let validation_code = api.parachain_code(&relay_parent, para_id)? - .ok_or_else(|| Error::InactiveParachain(para_id))?; - - Ok((local_validation, global_validation, validation_code)) -} - -/// Does full-pipeline validation of a collation with provided contextual parameters. -pub fn full_output_validation_with_api

( - validation_pool: Option<&ValidationPool>, - api: &P, - collation: &CollationInfo, - pov_block: &PoVBlock, - expected_relay_parent: &Hash, - max_block_data_size: Option, - n_validators: usize, - spawner: impl SpawnNamed + 'static, -) -> Result where - P: ProvideRuntimeApi, - P::Api: ParachainHost, -{ - let para_id = collation.parachain_index; - let (local_validation, global_validation, validation_code) - = validation_params(&*api, collation.relay_parent, para_id)?; - - // put the parameters through the validation pipeline, producing - // erasure chunks. - let encoded_pov = pov_block.encode(); - basic_checks( - &collation, - &expected_relay_parent, - max_block_data_size, - &encoded_pov, - ) - .and_then(|()| { - let res = validate( - validation_pool, - &collation, - &pov_block, - &local_validation, - &global_validation, - &validation_code, - spawner, - ); - - match res { - Err(ref err) => log::debug!( - target: "validation", - "Failed to validate PoVBlock for parachain ({}): {:?}", - para_id, - err, - ), - Ok(_) => log::debug!( - target: "validation", - "Successfully validated PoVBlock for parachain ({}).", - para_id, - ), - } - - res - }) - .and_then(|validated| validated.full_output(n_validators)) -} - -#[cfg(test)] -mod tests { - use super::*; - use parachain::primitives::ParachainDispatchOrigin; - - fn add_msg(size: usize, msgs: &mut Vec) { - let msg = UpwardMessage { data: vec![0; size], origin: ParachainDispatchOrigin::Parachain }; - msgs.push(msg); - } - - #[test] - fn validate_upward_messages_works() { - let fee_schedule = FeeSchedule { - base: 1000, - per_byte: 10, - }; - let free_balance = 1_000_000; - let mut msgs = Vec::new(); - - add_msg(100, &mut msgs); - assert_eq!(2000, validate_upward_messages(&msgs, fee_schedule, free_balance).unwrap()); - - add_msg(100, &mut msgs); - assert_eq!(4000, validate_upward_messages(&msgs, fee_schedule, free_balance).unwrap()); - - add_msg((1_000_000 - 4000 - 1000) / 10, &mut msgs); - assert_eq!(1_000_000, validate_upward_messages(&msgs, fee_schedule, free_balance).unwrap()); - - // cannot pay fee. - add_msg(1, &mut msgs); - let err = validate_upward_messages(&msgs, fee_schedule, free_balance).unwrap_err(); - assert!(matches!(err, Error::CouldNotCoverFee)); - } -} diff --git a/validation/src/shared_table/includable.rs b/validation/src/shared_table/includable.rs deleted file mode 100644 index 317b9e0f7bfe..000000000000 --- a/validation/src/shared_table/includable.rs +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Implements a future which resolves when all of the candidates referenced are includable. - -use std::collections::HashMap; -use futures::channel::oneshot; -use polkadot_primitives::v0::Hash; - -/// Track includability of a set of candidates, -pub(super) fn track>(candidates: I) - -> (IncludabilitySender, oneshot::Receiver<()>) { - - let (tx, rx) = oneshot::channel(); - let tracking: HashMap<_, _> = candidates.into_iter().collect(); - let includable_count = tracking.values().filter(|x| **x).count(); - - let mut sender = IncludabilitySender { - tracking, - includable_count, - sender: Some(tx), - }; - - sender.try_complete(); - - (sender, rx) -} - -/// The sending end of the includability sender. -pub(super) struct IncludabilitySender { - tracking: HashMap, - includable_count: usize, - sender: Option>, -} - -impl IncludabilitySender { - /// update the inner candidate. wakes up the task as necessary. - /// returns `Err(Canceled)` if the other end has hung up. - /// - /// returns `true` when this is completed and should be destroyed. - pub fn update_candidate(&mut self, candidate: Hash, includable: bool) -> bool { - use std::collections::hash_map::Entry; - - match self.tracking.entry(candidate) { - Entry::Vacant(_) => {} - Entry::Occupied(mut entry) => { - let old = entry.insert(includable); - if !old && includable { - self.includable_count += 1; - } else if old && !includable { - self.includable_count -= 1; - } - } - } - - self.try_complete() - } - - /// whether the sender is completed. - pub fn is_complete(&self) -> bool { - self.sender.is_none() - } - - fn try_complete(&mut self) -> bool { - if self.includable_count == self.tracking.len() { - if let Some(sender) = self.sender.take() { - let _ = sender.send(()); - } - - true - } else { - false - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::executor::block_on; - - #[test] - fn it_works() { - let hash1 = [1; 32].into(); - let hash2 = [2; 32].into(); - let hash3 = [3; 32].into(); - - let (mut sender, recv) = track([ - (hash1, true), - (hash2, true), - (hash2, false), // overwrite should favor latter. - (hash3, true), - ].iter().cloned()); - - assert!(!sender.is_complete()); - - // true -> false transition is possible and should be handled. - sender.update_candidate(hash1, false); - assert!(!sender.is_complete()); - - sender.update_candidate(hash2, true); - assert!(!sender.is_complete()); - - sender.update_candidate(hash1, true); - assert!(sender.is_complete()); - - block_on(recv).unwrap(); - } -} diff --git a/validation/src/shared_table/mod.rs b/validation/src/shared_table/mod.rs deleted file mode 100644 index 1e77c21b38cc..000000000000 --- a/validation/src/shared_table/mod.rs +++ /dev/null @@ -1,1045 +0,0 @@ -// Copyright 2017-2020 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 . - -//! Parachain statement table meant to be shared with a message router -//! and a consensus proposer. - -use std::collections::hash_map::{HashMap, Entry}; -use std::sync::Arc; - -use availability_store::{Store as AvailabilityStore}; -use table::{v0 as table_v0, Table, Context as TableContextTrait}; -use polkadot_primitives::v0::{ - Block, Hash, - Id as ParaId, AbridgedCandidateReceipt, ValidatorPair, ValidatorId, - AttestedCandidate, ParachainHost, PoVBlock, ValidatorIndex, SigningContext, - ValidatorSignature, -}; - -use parking_lot::Mutex; -use futures::prelude::*; -use futures::channel::oneshot; -use log::{warn, debug}; -use bitvec::bitvec; - -use super::GroupInfo; -use self::includable::IncludabilitySender; -use primitives::{Pair, traits::SpawnNamed}; -use sp_api::ProvideRuntimeApi; - -use crate::pipeline::{FullOutput, ValidationPool}; -use crate::Error; - -mod includable; - -pub use table_v0::{SignedStatement, Statement}; -pub use table::generic::Statement as GenericStatement; - -struct TableContext { - signing_context: SigningContext, - key: Option>, - groups: HashMap, - validators: Vec, -} - -impl TableContextTrait for TableContext { - type AuthorityId = ValidatorIndex; - type Digest = Hash; - type GroupId = ParaId; - type Signature = ValidatorSignature; - type Candidate = AbridgedCandidateReceipt; - - fn candidate_digest(candidate: &AbridgedCandidateReceipt) -> Hash { - candidate.hash() - } - - fn candidate_group(candidate: &AbridgedCandidateReceipt) -> ParaId { - candidate.parachain_index - } - - fn is_member_of(&self, authority: &ValidatorIndex, group: &ParaId) -> bool { - let key = match self.validators.get(*authority as usize) { - Some(val) => val, - None => return false, - }; - - self.groups.get(group).map_or(false, |g| g.validity_guarantors.get(&key).is_some()) - } - - fn requisite_votes(&self, group: &ParaId) -> usize { - self.groups.get(group).map_or(usize::max_value(), |g| g.needed_validity) - } -} - -impl TableContext { - fn local_id(&self) -> Option { - self.key.as_ref().map(|k| k.public()) - } - - fn local_index(&self) -> Option { - self.local_id().and_then(|id| - self.validators - .iter() - .enumerate() - .find(|(_, k)| k == &&id) - .map(|(i, _)| i as ValidatorIndex) - ) - } - - fn sign_statement(&self, statement: table_v0::Statement) -> Option { - self.local_index().and_then(move |sender| - self.key.as_ref() - .map(|key| crate::sign_table_statement( - &statement, - key, - &self.signing_context, - ).into() - ) - .map(move |signature| table_v0::SignedStatement { statement, signature, sender }) - ) - } -} - -pub(crate) enum Validation { - Valid(PoVBlock), - Invalid(PoVBlock), // should take proof. -} - -enum ValidationWork { - Done(Validation), - InProgress, - Error(String), -} - -#[cfg(test)] -impl ValidationWork { - fn is_in_progress(&self) -> bool { - match *self { - ValidationWork::InProgress => true, - _ => false, - } - } - - fn is_done(&self) -> bool { - match *self { - ValidationWork::Done(_) => true, - _ => false, - } - } -} - -// A shared table object. -struct SharedTableInner { - table: Table, - trackers: Vec, - availability_store: AvailabilityStore, - validated: HashMap, - validation_pool: Option, -} - -impl SharedTableInner { - // Import a single statement. Provide a handle to a table router and a function - // used to determine if a referenced candidate is valid. - // - // the statement producer, if any, will produce only statements concerning the same candidate - // as the one just imported - fn import_remote_statement( - &mut self, - context: &TableContext, - fetch_pov_block: impl Fn(&AbridgedCandidateReceipt) -> Fetch, - statement: table_v0::SignedStatement, - max_block_data_size: Option, - ) -> Option> { - let summary = self.table.import_statement(context, statement)?; - self.update_trackers(&summary.candidate, context); - - let local_index = context.local_index()?; - let para_member = context.is_member_of(&local_index, &summary.group_id); - let digest = &summary.candidate; - - // TODO: consider a strategy based on the number of candidate votes as well. - // https://github.com/paritytech/polkadot/issues/218 - let do_validation = para_member && match self.validated.entry(digest.clone()) { - Entry::Occupied(_) => false, - Entry::Vacant(entry) => { - entry.insert(ValidationWork::InProgress); - true - } - }; - - let work = if do_validation { - match self.table.get_candidate(&digest) { - None => { - let message = format!( - "Table inconsistency detected. Summary returned for candidate {} \ - but receipt not present in table.", - digest, - ); - - warn!(target: "validation", "{}", message); - self.validated.insert(digest.clone(), ValidationWork::Error(message)); - None - } - Some(candidate) => { - let fetch = fetch_pov_block(candidate); - - Some(Work { - candidate_receipt: candidate.clone(), - fetch, - }) - } - } - } else { - None - }; - - work.map(|work| ParachainWork { - validation_pool: self.validation_pool.clone(), - availability_store: self.availability_store.clone(), - relay_parent: context.signing_context.parent_hash.clone(), - work, - max_block_data_size, - n_validators: context.validators.len(), - }) - } - - fn update_trackers(&mut self, candidate: &Hash, context: &TableContext) { - let includable = self.table.candidate_includable(candidate, context); - for i in (0..self.trackers.len()).rev() { - if self.trackers[i].update_candidate(candidate.clone(), includable) { - self.trackers.swap_remove(i); - } - } - } -} - -/// Produced after validating a candidate. -pub struct Validated { - /// A statement about the validity of the candidate. - statement: table_v0::Statement, - /// The result of validation. - result: Validation, -} - -impl Validated { - /// Note that we've validated a candidate with given hash and it is bad. - pub fn known_bad(hash: Hash, collation: PoVBlock) -> Self { - Validated { - statement: GenericStatement::Invalid(hash), - result: Validation::Invalid(collation), - } - } - - /// Note that we've validated a candidate with given hash and it is good. - /// outgoing message required. - pub fn known_good(hash: Hash, collation: PoVBlock) -> Self { - Validated { - statement: GenericStatement::Valid(hash), - result: Validation::Valid(collation), - } - } - - /// Note that we've collated a candidate. - /// outgoing message required. - pub fn collated_local( - receipt: AbridgedCandidateReceipt, - collation: PoVBlock, - ) -> Self { - Validated { - statement: GenericStatement::Candidate(receipt), - result: Validation::Valid(collation), - } - } - - /// Get a reference to the proof-of-validation block. - pub fn pov_block(&self) -> &PoVBlock { - match self.result { - Validation::Valid(ref b) | Validation::Invalid(ref b) => b, - } - } -} - -/// Future that performs parachain validation work. -pub struct ParachainWork { - validation_pool: Option, - work: Work, - relay_parent: Hash, - availability_store: AvailabilityStore, - max_block_data_size: Option, - n_validators: usize, -} - -impl ParachainWork { - /// Prime the parachain work with an API reference for extracting - /// chain information. - pub fn prime>( - self, - api: Arc

, - spawner: impl SpawnNamed + Clone + Unpin + 'static, - ) -> PrimedParachainWork< - Fetch, - impl Send + FnMut(&PoVBlock, &AbridgedCandidateReceipt) - -> Result + Unpin, - > - where - P: Send + Sync + 'static, - P::Api: ParachainHost, - { - let max_block_data_size = self.max_block_data_size; - let n_validators = self.n_validators; - let expected_relay_parent = self.relay_parent; - - let pool = self.validation_pool.clone(); - let validate = move |pov_block: &PoVBlock, candidate: &AbridgedCandidateReceipt| { - let collation_info = candidate.to_collation_info(); - let full_output = crate::pipeline::full_output_validation_with_api( - pool.as_ref(), - &*api, - &collation_info, - pov_block, - &expected_relay_parent, - max_block_data_size, - n_validators, - spawner.clone(), - )?; - - full_output.check_consistency(candidate)?; - Ok(full_output) - }; - - PrimedParachainWork { inner: self, validate } - } - - /// Prime the parachain work with a custom validation function. - #[cfg(test)] - pub fn prime_with(self, validate: F) -> PrimedParachainWork - where F: FnMut(&PoVBlock, &AbridgedCandidateReceipt) - -> Result - { - PrimedParachainWork { inner: self, validate } - } -} - -struct Work { - candidate_receipt: AbridgedCandidateReceipt, - fetch: Fetch -} - -/// Primed statement producer. -pub struct PrimedParachainWork { - inner: ParachainWork, - validate: F, -} - -impl PrimedParachainWork - where - Fetch: Future> + Unpin, - F: FnMut(&PoVBlock, &AbridgedCandidateReceipt) -> Result + Unpin, - Err: From, -{ - pub async fn validate(self) -> Result { - let candidate = self.inner.work.candidate_receipt; - let pov_block = self.inner.work.fetch.await?; - - let mut validate = self.validate; - let relay_parent = self.inner.relay_parent; - - // create a wrapper around the custom validation function that does - // some more general pre and post checks. - let mut validate = move |pov_block: &_, candidate: &AbridgedCandidateReceipt| { - if candidate.relay_parent != relay_parent { - return Err(Error::DisallowedRelayParent(candidate.relay_parent)); - } - - let full_output = validate(pov_block, candidate)?; - - if full_output.commitments != candidate.commitments { - return Err(Error::CommitmentsMismatch); - } - - Ok(full_output) - }; - - let validation_res = (validate)(&pov_block, &candidate); - let candidate_hash = candidate.hash(); - - debug!(target: "validation", "Making validity statement about candidate {}: is_good? {:?}", - candidate_hash, validation_res.is_ok()); - - match validation_res { - Err(err) => { - debug!(target: "validation", "candidate is invalid: {}", err); - Ok(Validated { - statement: GenericStatement::Invalid(candidate_hash), - result: Validation::Invalid(pov_block), - }) - } - Ok(full_output) => { - // make data and all erasure chunks available. The chunk - // must be fully available before we add the chunks. - self.inner.availability_store.make_available( - candidate_hash, - full_output.available_data, - ).await?; - self.inner.availability_store.add_erasure_chunks( - candidate, - full_output.n_validators as _, - full_output.erasure_chunks, - ).await?; - - Ok(Validated { - statement: GenericStatement::Valid(candidate_hash), - result: Validation::Valid(pov_block), - }) - } - } - } -} - -/// A shared table object. -pub struct SharedTable { - context: Arc, - inner: Arc>, - max_block_data_size: Option, -} - -impl Clone for SharedTable { - fn clone(&self) -> Self { - Self { - context: self.context.clone(), - inner: self.inner.clone(), - max_block_data_size: self.max_block_data_size, - } - } -} - -impl SharedTable { - /// Create a new shared table. - /// - /// Provide the key to sign with, and the parent hash of the relay chain - /// block being built. - pub fn new( - validators: Vec, - groups: HashMap, - key: Option>, - signing_context: SigningContext, - availability_store: AvailabilityStore, - max_block_data_size: Option, - validation_pool: Option, - ) -> Self { - SharedTable { - context: Arc::new(TableContext { groups, key, signing_context, validators: validators.clone(), }), - max_block_data_size, - inner: Arc::new(Mutex::new(SharedTableInner { - table: Table::default(), - validated: HashMap::new(), - trackers: Vec::new(), - availability_store, - validation_pool, - })) - } - } - - /// Get the parent hash this table should hold statements localized to. - pub fn signing_context(&self) -> &SigningContext { - &self.context.signing_context - } - - /// Get the local validator session key. - pub fn session_key(&self) -> Option { - self.context.local_id() - } - - /// Get group info. - pub fn group_info(&self) -> &HashMap { - &self.context.groups - } - - /// Import a single statement with remote source, whose signature has already been checked. - /// - /// Validity and invalidity statements are only valid if the corresponding - /// candidate has already been imported. - /// - /// The ParachainWork, if any, will produce only statements concerning the same candidate - /// as the one just imported - pub fn import_remote_statement( - &self, - fetch_pov_block: impl Fn(&AbridgedCandidateReceipt) -> Fetch, - statement: table_v0::SignedStatement, - ) -> Option> { - self.inner.lock().import_remote_statement( - &*self.context, - fetch_pov_block, - statement, - self.max_block_data_size, - ) - } - - /// Import many statements at once. - /// - /// Provide an iterator yielding remote, pre-checked statements. - /// Validity and invalidity statements are only valid if the corresponding - /// candidate has already been imported. - /// - /// The ParachainWork, if any, will produce only statements concerning the same candidate - /// as the one just imported - pub fn import_remote_statements( - &self, - fetch_pov_block: impl Fn(&AbridgedCandidateReceipt) -> Fetch, - iterable: I, - ) -> U - where - I: IntoIterator, - U: ::std::iter::FromIterator>>, - { - let mut inner = self.inner.lock(); - - iterable.into_iter().map(move |statement| { - inner.import_remote_statement( - &*self.context, - &fetch_pov_block, - statement, - self.max_block_data_size, - ) - }).collect() - } - - /// Sign and import the result of candidate validation. Returns `None` if the table - /// was instantiated without a local key. Otherwise, returns a copy of the signed - /// statement. - pub fn import_validated(&self, validated: Validated) - -> Option - { - let digest = match validated.statement { - GenericStatement::Candidate(ref c) => c.hash(), - GenericStatement::Valid(h) | GenericStatement::Invalid(h) => h, - }; - - let signed_statement = self.context.sign_statement(validated.statement); - - if let Some(ref signed) = signed_statement { - let mut inner = self.inner.lock(); - inner.table.import_statement(&*self.context, signed.clone()); - inner.validated.insert(digest, ValidationWork::Done(validated.result)); - } - - signed_statement - } - - /// Execute a closure using a specific candidate. - /// - /// Deadlocks if called recursively. - pub fn with_candidate(&self, digest: &Hash, f: F) -> U - where F: FnOnce(Option<&AbridgedCandidateReceipt>) -> U - { - let inner = self.inner.lock(); - f(inner.table.get_candidate(digest)) - } - - /// Get a set of candidates that can be proposed. - pub fn proposed_set(&self) -> Vec { - use table::generic::{ValidityAttestation as GAttestation}; - use polkadot_primitives::v0::ValidityAttestation; - - // we transform the types of the attestations gathered from the table - // into the type expected by the runtime. This may do signature - // aggregation in the future. - let table_attestations = self.inner.lock().table.proposed_candidates(&*self.context); - table_attestations.into_iter() - .map(|attested| { - let mut validity_votes: Vec<_> = attested.validity_votes.into_iter().map(|(id, a)| { - (id as usize, match a { - GAttestation::Implicit(s) => ValidityAttestation::Implicit(s), - GAttestation::Explicit(s) => ValidityAttestation::Explicit(s), - }) - }).collect(); - validity_votes.sort_by(|(id1, _), (id2, _)| id1.cmp(id2)); - - let mut validator_indices = bitvec![ - bitvec::order::Lsb0, u8; - 0; - validity_votes.last().map(|(i, _)| i + 1).unwrap_or_default() - ]; - for (id, _) in &validity_votes { - validator_indices.set(*id, true); - } - - AttestedCandidate { - candidate: attested.candidate, - validity_votes: validity_votes.into_iter().map(|(_, a)| a).collect(), - validator_indices, - } - }).collect() - } - - /// Get the number of total parachains. - pub fn num_parachains(&self) -> usize { - self.group_info().len() - } - - /// Get the number of parachains whose candidates may be included. - pub fn includable_count(&self) -> usize { - self.inner.lock().table.includable_count() - } - - /// Get all witnessed misbehavior. - pub fn get_misbehavior(&self) -> HashMap { - self.inner.lock().table.get_misbehavior().clone() - } - - /// Track includability of a given set of candidate hashes. - pub fn track_includability(&self, iterable: I) -> oneshot::Receiver<()> - where I: IntoIterator - { - let mut inner = self.inner.lock(); - - let (tx, rx) = includable::track(iterable.into_iter().map(|x| { - let includable = inner.table.candidate_includable(&x, &*self.context); - (x, includable) - })); - - if !tx.is_complete() { - inner.trackers.push(tx); - } - - rx - } - - /// Returns id of the validator corresponding to the given index. - pub fn index_to_id(&self, index: ValidatorIndex) -> Option { - self.context.validators.get(index as usize).cloned() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_keyring::Sr25519Keyring; - use polkadot_primitives::v0::{ - BlockData, ErasureChunk, AvailableData, - }; - use polkadot_erasure_coding::{self as erasure}; - use availability_store::ErasureNetworking; - use futures::future; - use futures::executor::block_on; - use std::pin::Pin; - - fn pov_block_with_data(data: Vec) -> PoVBlock { - PoVBlock { - block_data: BlockData(data), - } - } - - #[derive(Clone)] - struct DummyErasureNetworking; - - impl ErasureNetworking for DummyErasureNetworking { - type Error = String; - - fn fetch_erasure_chunk( - &self, - _candidate_hash: &Hash, - _index: u32, - ) -> Pin> + Send>> { - future::pending().boxed() - } - - fn distribute_erasure_chunk( - &self, - _candidate_hash: Hash, - _chunk: ErasureChunk, - ) {} - } - - fn lazy_fetch_pov() - -> Box< - dyn Fn(&AbridgedCandidateReceipt) -> future::Ready< - Result - > - > - { - Box::new(|_| future::ok(pov_block_with_data(vec![1, 2, 3, 4, 5]))) - } - - #[test] - fn statement_triggers_fetch_and_evaluate() { - let mut groups = HashMap::new(); - - let para_id = ParaId::from(1); - let parent_hash = Hash::default(); - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash: parent_hash.clone(), - }; - - let local_key = Sr25519Keyring::Alice.pair(); - let local_id: ValidatorId = local_key.public().into(); - let local_key: Arc = Arc::new(local_key.into()); - - let validity_other_key = Sr25519Keyring::Bob.pair(); - let validity_other: ValidatorId = validity_other_key.public().into(); - let validity_other_index = 1; - - groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), - needed_validity: 2, - }); - - let shared_table = SharedTable::new( - [local_id, validity_other].to_vec(), - groups, - Some(local_key.clone()), - signing_context.clone(), - AvailabilityStore::new_in_memory(DummyErasureNetworking), - None, - None, - ); - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.relay_parent = parent_hash; - - let candidate_statement = GenericStatement::Candidate(candidate); - - let signature = crate::sign_table_statement( - &candidate_statement, - &validity_other_key.into(), - &signing_context, - ); - let signed_statement = table::generic::SignedStatement { - statement: candidate_statement, - signature: signature.into(), - sender: validity_other_index, - }; - - shared_table.import_remote_statement( - lazy_fetch_pov(), - signed_statement, - ).expect("candidate and local validity group are same"); - } - - #[test] - fn statement_triggers_fetch_and_validity() { - let mut groups = HashMap::new(); - - let para_id = ParaId::from(1); - let parent_hash = Hash::default(); - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash: parent_hash.clone(), - }; - - let local_key = Sr25519Keyring::Alice.pair(); - let local_id: ValidatorId = local_key.public().into(); - let local_key: Arc = Arc::new(local_key.into()); - - let validity_other_key = Sr25519Keyring::Bob.pair(); - let validity_other: ValidatorId = validity_other_key.public().into(); - let validity_other_index = 1; - - groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), - needed_validity: 1, - }); - - let shared_table = SharedTable::new( - [local_id, validity_other].to_vec(), - groups, - Some(local_key.clone()), - signing_context.clone(), - AvailabilityStore::new_in_memory(DummyErasureNetworking), - None, - None, - ); - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.relay_parent = parent_hash; - - let candidate_statement = GenericStatement::Candidate(candidate); - - let signature = crate::sign_table_statement( - &candidate_statement, - &validity_other_key.into(), - &signing_context, - ); - let signed_statement = table::generic::SignedStatement { - statement: candidate_statement, - signature: signature.into(), - sender: validity_other_index, - }; - - shared_table.import_remote_statement( - lazy_fetch_pov(), - signed_statement, - ).expect("should produce work"); - } - - #[test] - fn evaluate_makes_block_data_available() { - let store = AvailabilityStore::new_in_memory(DummyErasureNetworking); - let relay_parent = [0; 32].into(); - let para_id = 5.into(); - let pov_block = pov_block_with_data(vec![1, 2, 3]); - let pov_block_hash = pov_block.hash(); - let local_index = 0; - let n_validators = 2; - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.relay_parent = relay_parent; - candidate.pov_block_hash = pov_block_hash; - - let candidate_hash = candidate.hash(); - - store.note_validator_index_and_n_validators( - &relay_parent, - local_index as u32, - n_validators as u32, - ).unwrap(); - - let producer: ParachainWork>> = ParachainWork { - work: Work { - candidate_receipt: candidate, - fetch: future::ok(pov_block.clone()), - }, - relay_parent, - availability_store: store.clone(), - max_block_data_size: None, - n_validators, - validation_pool: None, - }; - - for i in 0..n_validators { - assert!(store.get_erasure_chunk(&candidate_hash, i).is_none()); - } - - let validated = block_on(producer.prime_with(|_, _| Ok( - FullOutput { - available_data: AvailableData { - pov_block: pov_block.clone(), - omitted_validation: Default::default(), - }, - erasure_chunks: (0..n_validators).map(|i| ErasureChunk { - chunk: vec![1, 2, 3], - index: i as u32, - proof: vec![], - }).collect(), - commitments: Default::default(), - n_validators, - } - )).validate()).unwrap(); - - assert_eq!(validated.pov_block(), &pov_block); - assert_eq!(validated.statement, GenericStatement::Valid(candidate_hash)); - - for i in 0..n_validators { - assert!(store.get_erasure_chunk(&candidate_hash, i).is_some()); - } - } - - #[test] - fn full_availability() { - let store = AvailabilityStore::new_in_memory(DummyErasureNetworking); - let relay_parent = [0; 32].into(); - let para_id = 5.into(); - let pov_block = pov_block_with_data(vec![1, 2, 3]); - let pov_block_hash = pov_block.hash(); - let local_index = 0; - let n_validators = 2; - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.pov_block_hash = pov_block_hash; - candidate.relay_parent = relay_parent; - - let candidate_hash = candidate.hash(); - - let available_data = AvailableData { - pov_block: pov_block.clone(), - omitted_validation: Default::default(), - }; - - let chunks = erasure::obtain_chunks_v0(n_validators, &available_data).unwrap(); - - store.note_validator_index_and_n_validators( - &relay_parent, - local_index as u32, - n_validators as u32, - ).unwrap(); - - let producer = ParachainWork { - work: Work { - candidate_receipt: candidate, - fetch: future::ok::<_, ::std::io::Error>(pov_block.clone()), - }, - relay_parent, - availability_store: store.clone(), - max_block_data_size: None, - n_validators, - validation_pool: None, - }; - - let validated = block_on(producer.prime_with(|_, _| Ok( - FullOutput { - available_data: AvailableData { - pov_block: pov_block.clone(), - omitted_validation: Default::default(), - }, - erasure_chunks: (0..n_validators).map(|i| ErasureChunk { - chunk: chunks[i].clone(), - index: i as u32, - proof: vec![], - }).collect(), - commitments: Default::default(), - n_validators, - } - )).validate()).unwrap(); - - assert_eq!(validated.pov_block(), &pov_block); - - assert_eq!(store.execution_data(&candidate_hash).unwrap().pov_block, pov_block); - } - - #[test] - fn does_not_dispatch_work_after_starting_validation() { - let mut groups = HashMap::new(); - - let para_id = ParaId::from(1); - let parent_hash = Hash::default(); - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash: parent_hash.clone(), - }; - - let local_key = Sr25519Keyring::Alice.pair(); - let local_id: ValidatorId = local_key.public().into(); - let local_key: Arc = Arc::new(local_key.into()); - - let validity_other_key = Sr25519Keyring::Bob.pair(); - let validity_other: ValidatorId = validity_other_key.public().into(); - let validity_other_index = 1; - - groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), - needed_validity: 1, - }); - - let shared_table = SharedTable::new( - [local_id, validity_other].to_vec(), - groups, - Some(local_key.clone()), - signing_context.clone(), - AvailabilityStore::new_in_memory(DummyErasureNetworking), - None, - None, - ); - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.relay_parent = parent_hash; - - let candidate_hash = candidate.hash(); - let candidate_statement = GenericStatement::Candidate(candidate); - - let signature = crate::sign_table_statement( - &candidate_statement, - &validity_other_key.into(), - &signing_context, - ); - let signed_statement = table::generic::SignedStatement { - statement: candidate_statement, - signature: signature.into(), - sender: validity_other_index, - }; - - let _a = shared_table.import_remote_statement( - lazy_fetch_pov(), - signed_statement.clone(), - ).expect("should produce work"); - - assert!(shared_table.inner.lock().validated.get(&candidate_hash) - .expect("validation has started").is_in_progress()); - - let b = shared_table.import_remote_statement( - lazy_fetch_pov(), - signed_statement.clone(), - ); - - assert!(b.is_none(), "cannot work when validation has started"); - } - - #[test] - fn does_not_dispatch_after_local_candidate() { - let mut groups = HashMap::new(); - - let para_id = ParaId::from(1); - let pov_block = pov_block_with_data(vec![1, 2, 3]); - let parent_hash = Hash::default(); - let signing_context = SigningContext { - session_index: Default::default(), - parent_hash: parent_hash.clone(), - }; - - let local_key = Sr25519Keyring::Alice.pair(); - let local_id: ValidatorId = local_key.public().into(); - let local_key: Arc = Arc::new(local_key.into()); - - let validity_other_key = Sr25519Keyring::Bob.pair(); - let validity_other: ValidatorId = validity_other_key.public().into(); - - groups.insert(para_id, GroupInfo { - validity_guarantors: [local_id.clone(), validity_other.clone()].iter().cloned().collect(), - needed_validity: 1, - }); - - let shared_table = SharedTable::new( - [local_id, validity_other].to_vec(), - groups, - Some(local_key.clone()), - signing_context.clone(), - AvailabilityStore::new_in_memory(DummyErasureNetworking), - None, - None, - ); - - let mut candidate = AbridgedCandidateReceipt::default(); - candidate.parachain_index = para_id; - candidate.relay_parent = parent_hash; - - let candidate_hash = candidate.hash(); - let signed_statement = shared_table.import_validated(Validated::collated_local( - candidate, - pov_block, - )).unwrap(); - - assert!(shared_table.inner.lock().validated.get(&candidate_hash) - .expect("validation has started").is_done()); - - let a = shared_table.import_remote_statement( - lazy_fetch_pov(), - signed_statement, - ); - - assert!(a.is_none()); - } -} diff --git a/validation/src/validation_service/mod.rs b/validation/src/validation_service/mod.rs deleted file mode 100644 index d30b52ef5cac..000000000000 --- a/validation/src/validation_service/mod.rs +++ /dev/null @@ -1,802 +0,0 @@ -// Copyright 2017-2020 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 . - -//! The validation service is a long-running future that creates and manages parachain attestation -//! instances. -//! -//! As soon as we import a new chain head, we start a parachain attestation session on top of it. -//! The block authorship service may want access to the attestation session, and for that reason -//! we expose a `ServiceHandle` which can be used to request a copy of it. -//! -//! In fact, the import notification and request from the block production pipeline may race to be -//! the first one to create the instant, but the import notification will usually win. -//! -//! These attestation sessions are kept live until they are periodically garbage-collected. - -use std::{time::{Duration, Instant}, sync::Arc, pin::Pin, collections::HashMap}; - -use crate::pipeline::FullOutput; -use sc_client_api::{BlockchainEvents, BlockBackend}; -use consensus::SelectChain; -use futures::prelude::*; -use polkadot_primitives::v0::{ - Block, Hash, BlockId, - Chain, ParachainHost, Id as ParaId, ValidatorIndex, ValidatorId, ValidatorPair, - CollationInfo, SigningContext, -}; -use keystore::KeyStorePtr; -use sp_api::{ProvideRuntimeApi, ApiExt}; -use runtime_primitives::traits::HashFor; -use availability_store::Store as AvailabilityStore; -use primitives::traits::SpawnNamed; - -use ansi_term::Colour; -use log::{warn, info, debug, trace}; - -use super::{Network, Collators, SharedTable, TableRouter}; -use crate::Error; -use crate::pipeline::ValidationPool; - -// Remote processes may request for a validation instance to be cloned or instantiated. -// They send a oneshot channel. -type ValidationInstanceRequest = ( - Hash, - futures::channel::oneshot::Sender>, -); - -/// A handle to a single instance of parachain validation, which is pinned to -/// a specific relay-chain block. This is the instance that should be used when -/// constructing any -#[derive(Clone)] -pub(crate) struct ValidationInstanceHandle { - table: Arc, - started: Instant, -} - -impl ValidationInstanceHandle { - /// Access the underlying table of attestations on parachain candidates. - pub(crate) fn table(&self) -> &Arc { - &self.table - } - - /// The moment we started this validation instance. - pub(crate) fn started(&self) -> Instant { - self.started.clone() - } -} - -/// A handle to the service. This can be used to create a block-production environment. -#[derive(Clone)] -pub struct ServiceHandle { - sender: futures::channel::mpsc::Sender, -} - -impl ServiceHandle { - /// Requests instantiation or cloning of a validation instance from the service. - /// - /// This can fail if the service task has shut down for some reason. - pub(crate) async fn get_validation_instance(self, relay_parent: Hash) - -> Result - { - let mut sender = self.sender; - let instance_rx = loop { - let (instance_tx, instance_rx) = futures::channel::oneshot::channel(); - match sender.send((relay_parent, instance_tx)).await { - Ok(()) => break instance_rx, - Err(e) => if !e.is_full() { - // Sink::send should be doing `poll_ready` before start-send, - // so this should only happen when there is a race. - return Err(Error::ValidationServiceDown) - }, - } - }; - - instance_rx.map_err(|_| Error::ValidationServiceDown).await.and_then(|x| x) - } -} - -fn interval(duration: Duration) -> impl Stream + Send + Unpin { - stream::unfold((), move |_| { - futures_timer::Delay::new(duration).map(|_| Some(((), ()))) - }).map(drop) -} - -/// A builder for the validation service. -pub struct ServiceBuilder { - /// The underlying blockchain client. - pub client: Arc

, - /// A handle to the network object used to communicate. - pub network: N, - /// A handle to the collator pool we are using. - pub collators: C, - /// A handle to a background executor. - pub spawner: SP, - /// A handle to the availability store. - pub availability_store: AvailabilityStore, - /// A chain selector for determining active leaves in the block-DAG. - pub select_chain: SC, - /// The keystore which holds the signing keys. - pub keystore: KeyStorePtr, - /// The maximum block-data size in bytes. - pub max_block_data_size: Option, -} - -impl ServiceBuilder where - C: Collators + Send + Sync + Unpin + 'static, - C::Collation: Send + Unpin + 'static, - P: BlockchainEvents + BlockBackend, - P: ProvideRuntimeApi + Send + Sync + 'static, - P::Api: ParachainHost, - N: Network + Send + Sync + 'static, - N::TableRouter: Send + 'static + Sync, - N::BuildTableRouter: Send + Unpin + 'static, - ::SendLocalCollation: Send, - SC: SelectChain + 'static, - SP: SpawnNamed + Clone + 'static, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, -{ - /// Build the service - this consists of a handle to it, as well as a background - /// future to be run to completion. - pub fn build(self) -> (ServiceHandle, impl Future + Send + 'static) { - const TIMER_INTERVAL: Duration = Duration::from_secs(30); - const CHAN_BUFFER: usize = 10; - - enum Message { - CollectGarbage, - // relay-parent, receiver for instance. - RequestInstance(ValidationInstanceRequest), - // new chain heads - import notification. - NotifyImport(sc_client_api::BlockImportNotification), - } - - let validation_pool = Some(ValidationPool::new()); - let mut parachain_validation = ParachainValidationInstances { - client: self.client.clone(), - network: self.network, - spawner: self.spawner.clone(), - availability_store: self.availability_store, - live_instances: HashMap::new(), - validation_pool: validation_pool.clone(), - collation_fetch: DefaultCollationFetch { - collators: self.collators, - validation_pool, - spawner: self.spawner, - }, - }; - - let client = self.client; - let select_chain = self.select_chain; - let keystore = self.keystore; - let max_block_data_size = self.max_block_data_size; - - let (tx, rx) = futures::channel::mpsc::channel(CHAN_BUFFER); - let interval = interval(TIMER_INTERVAL).map(|_| Message::CollectGarbage); - let import_notifications = client.import_notification_stream().map(Message::NotifyImport); - let instance_requests = rx.map(Message::RequestInstance); - let service = ServiceHandle { sender: tx }; - - let background_work = async move { - let message_stream = futures::stream::select(interval, instance_requests); - let mut message_stream = futures::stream::select(import_notifications, message_stream); - while let Some(message) = message_stream.next().await { - match message { - Message::CollectGarbage => { - match select_chain.leaves() { - Ok(leaves) => { - parachain_validation.retain(|h| leaves.contains(h)); - } - Err(e) => { - warn!("Error fetching leaves from client: {:?}", e); - } - } - } - Message::RequestInstance((relay_parent, sender)) => { - // Upstream will handle the failure case. - let _ = sender.send(parachain_validation.get_or_instantiate( - relay_parent, - &keystore, - max_block_data_size, - ).await); - } - Message::NotifyImport(notification) => { - let relay_parent = notification.hash; - if notification.is_new_best { - let res = parachain_validation.get_or_instantiate( - relay_parent, - &keystore, - max_block_data_size, - ).await; - - if let Err(e) = res { - warn!( - "Unable to start parachain validation on top of {:?}: {}", - relay_parent, e - ); - } - } - } - } - } - }; - - (service, background_work) - } -} - -/// Abstraction over `collation_fetch`. -pub(crate) trait CollationFetch { - /// Error type used by `collation_fetch`. - type Error: std::fmt::Debug; - - /// Fetch a collation for the given `parachain`. - fn collation_fetch

( - self, - parachain: ParaId, - relay_parent: Hash, - client: Arc

, - max_block_data_size: Option, - n_validators: usize, - ) -> Pin> + Send>> - where - P::Api: ParachainHost, - P: ProvideRuntimeApi + Send + Sync + 'static; -} - -#[derive(Clone)] -struct DefaultCollationFetch { - collators: C, - validation_pool: Option, - spawner: S, -} - -impl CollationFetch for DefaultCollationFetch - where - C: Collators + Send + Sync + Unpin + 'static, - C::Collation: Send + Unpin + 'static, - S: SpawnNamed + Clone + 'static, -{ - type Error = C::Error; - - fn collation_fetch

( - self, - parachain: ParaId, - relay_parent: Hash, - client: Arc

, - max_block_data_size: Option, - n_validators: usize, - ) -> Pin> + Send>> - where - P::Api: ParachainHost, - P: ProvideRuntimeApi + Send + Sync + 'static, - { - crate::collation::collation_fetch( - self.validation_pool, - parachain, - relay_parent, - self.collators, - client, - max_block_data_size, - n_validators, - self.spawner, - ).boxed() - } -} - -// finds the first key we are capable of signing with out of the given set of validators, -// if any. -fn signing_key(validators: &[ValidatorId], keystore: &KeyStorePtr) -> Option> { - let keystore = keystore.read(); - validators.iter() - .find_map(|v| { - keystore.key_pair::(&v).ok() - }) - .map(|pair| Arc::new(pair)) -} - -/// A live instance that is related to a relay chain validation round. -/// -/// It stores the `instance_handle` and the `_table_router`. -struct LiveInstance { - instance_handle: ValidationInstanceHandle, - /// Make sure we keep the table router alive, to respond/receive consensus messages. - _table_router: TR, -} - -/// Constructs parachain-agreement instances. -pub(crate) struct ParachainValidationInstances { - /// The client instance. - client: Arc

, - /// The backing network handle. - network: N, - /// handle to spawner - spawner: SP, - /// Store for extrinsic data. - availability_store: AvailabilityStore, - /// Live agreements. Maps relay chain parent hashes to attestation - /// instances. - live_instances: HashMap>, - /// The underlying validation pool of processes to use. - /// Only `None` in tests. - validation_pool: Option, - /// Used to fetch a collation. - collation_fetch: CF, -} - -impl ParachainValidationInstances where - N: Network, - N::Error: 'static, - P: ProvideRuntimeApi + Send + Sync + 'static, - P::Api: ParachainHost, - N::TableRouter: Send + 'static + Sync, - ::SendLocalCollation: Send, - N::BuildTableRouter: Unpin + Send + 'static, - SP: SpawnNamed + Send + 'static, - CF: CollationFetch + Clone + Send + Sync + 'static, - // Rust bug: https://github.com/rust-lang/rust/issues/24159 - sp_api::StateBackendFor: sp_api::StateBackend>, -{ - /// Get an attestation table for given parent hash. - /// - /// This starts a parachain agreement process on top of the parent hash if - /// one has not already started. - /// - /// Additionally, this will trigger broadcast of data to the new block's duty - /// roster. - async fn get_or_instantiate( - &mut self, - parent_hash: Hash, - keystore: &KeyStorePtr, - max_block_data_size: Option, - ) -> Result { - use primitives::Pair; - - if let Some(instance) = self.live_instances.get(&parent_hash) { - return Ok(instance.instance_handle.clone()); - } - - let id = BlockId::hash(parent_hash); - - let validators = self.client.runtime_api().validators(&id)?; - let sign_with = signing_key(&validators[..], keystore); - - let duty_roster = self.client.runtime_api().duty_roster(&id)?; - - let (group_info, local_duty) = crate::make_group_info( - duty_roster, - &validators, - sign_with.as_ref().map(|k| k.public()), - )?; - - if let Some(ref duty) = local_duty { - info!( - "✍️ Starting parachain attestation session (parent: {}) with active duty {}", - parent_hash, - Colour::Red.bold().paint(format!("{:?}", duty)), - ); - } else { - debug!( - "✍️ Starting parachain attestation session (parent: {}). No local duty..", - parent_hash, - ); - } - let active_parachains = self.client.runtime_api().active_parachains(&id)?; - - debug!(target: "validation", "Active parachains: {:?}", active_parachains); - - // If we are a validator, we need to store our index in this round in availability store. - // This will tell which erasure chunk we should store. - if let Some(ref local_duty) = local_duty { - if let Err(e) = self.availability_store.note_validator_index_and_n_validators( - &parent_hash, - local_duty.index, - validators.len() as u32, - ) { - warn!( - target: "validation", - "Failed to add validator index and n_validators to the availability-store: {:?}", e - ) - } - } - - let api = self.client.runtime_api(); - - let signing_context = if api.has_api_with::, _>( - &BlockId::hash(parent_hash), - |version| version >= 3, - )? { - api.signing_context(&id)? - } else { - trace!( - target: "validation", - "Expected runtime with ParachainHost version >= 3", - ); - SigningContext { - session_index: 0, - parent_hash, - } - }; - - let table = Arc::new(SharedTable::new( - validators.clone(), - group_info, - sign_with, - signing_context, - self.availability_store.clone(), - max_block_data_size, - self.validation_pool.clone(), - )); - - // The router will join the consensus gossip network. This is important - // to receive messages sent for the current round. - let router = match self.network.build_table_router( - table.clone(), - &validators, - ).await { - Ok(res) => res, - Err(e) => { - warn!(target: "validation", "Failed to build router: {:?}", e); - return Err(Error::CouldNotBuildTableRouter(format!("{:?}", e))) - } - }; - - if let Some((Chain::Parachain(id), index)) = local_duty.map(|d| (d.validation, d.index)) { - let n_validators = validators.len(); - let availability_store = self.availability_store.clone(); - let client = self.client.clone(); - let collation_fetch = self.collation_fetch.clone(); - let router = router.clone(); - - self.spawner.spawn( - "polkadot-parachain-validation-work", - launch_work( - move || collation_fetch.collation_fetch(id, parent_hash, client, max_block_data_size, n_validators), - availability_store, - router, - n_validators, - index, - ).boxed(), - ); - } - - let tracker = ValidationInstanceHandle { - table, - started: Instant::now(), - }; - - let live_instance = LiveInstance { - instance_handle: tracker.clone(), - _table_router: router, - }; - self.live_instances.insert(parent_hash, live_instance); - - Ok(tracker) - } - - /// Retain validation sessions matching predicate. - fn retain bool>(&mut self, mut pred: F) { - self.live_instances.retain(|k, _| pred(k)) - } -} - -// launch parachain work asynchronously. -async fn launch_work( - collation_fetch: impl FnOnce() -> CFF, - availability_store: AvailabilityStore, - router: impl TableRouter, - n_validators: usize, - local_id: ValidatorIndex, -) where - E: std::fmt::Debug, - CFF: Future> + Send, -{ - // fetch a local collation from connected collators. - let (collation_info, full_output) = match collation_fetch().await { - Ok(res) => res, - Err(e) => { - warn!(target: "validation", "Failed to collate candidate: {:?}", e); - return - } - }; - - let crate::pipeline::FullOutput { - commitments, - erasure_chunks, - available_data, - .. - } = full_output; - - let receipt = collation_info.into_receipt(commitments); - let pov_block = available_data.pov_block.clone(); - - if let Err(e) = availability_store.make_available( - receipt.hash(), - available_data, - ).await { - warn!( - target: "validation", - "Failed to make parachain block data available: {}", - e, - ); - } - - if let Err(e) = availability_store.clone().add_erasure_chunks( - receipt.clone(), - n_validators as _, - erasure_chunks.clone(), - ).await { - warn!(target: "validation", "Failed to add erasure chunks: {}", e); - } - - if let Err(e) = router.local_collation( - receipt, - pov_block, - (local_id, &erasure_chunks), - ).await { - warn!(target: "validation", "Failed to send local collation: {:?}", e); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures::{executor, future::ready, channel::mpsc}; - use availability_store::ErasureNetworking; - use polkadot_primitives::v0::{ - PoVBlock, AbridgedCandidateReceipt, ErasureChunk, ValidatorIndex, - CollationInfo, DutyRoster, GlobalValidationData, LocalValidationData, - Retriable, CollatorId, BlockData, Chain, AvailableData, SigningContext, ValidationCode, - }; - use runtime_primitives::traits::Block as BlockT; - use std::pin::Pin; - use sp_keyring::sr25519::Keyring; - use primitives::testing::TaskExecutor; - - /// Events fired while running mock implementations to follow execution. - enum Events { - BuildTableRouter, - CollationFetch, - LocalCollation, - } - - #[derive(Clone)] - struct MockNetwork(mpsc::UnboundedSender); - - impl Network for MockNetwork { - type Error = String; - type TableRouter = MockTableRouter; - type BuildTableRouter = Pin> + Send>>; - - fn build_table_router( - &self, - _: Arc, - _: &[ValidatorId], - ) -> Self::BuildTableRouter { - let event_sender = self.0.clone(); - async move { - event_sender.unbounded_send(Events::BuildTableRouter).expect("Send `BuildTableRouter`"); - - Ok(MockTableRouter(event_sender)) - }.boxed() - } - } - - #[derive(Clone)] - struct MockTableRouter(mpsc::UnboundedSender); - - impl TableRouter for MockTableRouter { - type Error = String; - type SendLocalCollation = Pin> + Send>>; - type FetchValidationProof = Box> + Unpin>; - - fn local_collation( - &self, - _: AbridgedCandidateReceipt, - _: PoVBlock, - _: (ValidatorIndex, &[ErasureChunk]), - ) -> Self::SendLocalCollation { - let sender = self.0.clone(); - - async move { - sender.unbounded_send(Events::LocalCollation).expect("Send `LocalCollation`"); - - Ok(()) - }.boxed() - } - - fn fetch_pov_block(&self, _: &AbridgedCandidateReceipt) -> Self::FetchValidationProof { - unimplemented!("Not required in tests") - } - } - - #[derive(Clone)] - struct MockErasureNetworking; - - impl ErasureNetworking for MockErasureNetworking { - type Error = String; - - fn fetch_erasure_chunk( - &self, - _: &Hash, - _: u32, - ) -> Pin> + Send>> { - ready(Err("Not required in tests".to_string())).boxed() - } - - fn distribute_erasure_chunk(&self, _: Hash, _: ErasureChunk) { - unimplemented!("Not required in tests") - } - } - - #[derive(Clone)] - struct MockCollationFetch(mpsc::UnboundedSender); - - impl CollationFetch for MockCollationFetch { - type Error = (); - - fn collation_fetch

( - self, - parachain: ParaId, - relay_parent: Hash, - _: Arc

, - _: Option, - n_validators: usize, - ) -> Pin> + Send>> { - let info = CollationInfo { - parachain_index: parachain, - relay_parent, - collator: Default::default(), - signature: Default::default(), - head_data: Default::default(), - pov_block_hash: Default::default(), - }; - - let available_data = AvailableData { - pov_block: PoVBlock { block_data: BlockData(Vec::new()) }, - omitted_validation: Default::default(), - }; - - let full_output = FullOutput { - available_data, - commitments: Default::default(), - erasure_chunks: Default::default(), - n_validators, - }; - - let sender = self.0; - - async move { - sender.unbounded_send(Events::CollationFetch).expect("`CollationFetch` event send"); - - Ok((info, full_output)) - }.boxed() - } - } - - #[derive(Clone)] - struct MockRuntimeApi { - validators: Vec, - duty_roster: DutyRoster, - } - - impl ProvideRuntimeApi for MockRuntimeApi { - type Api = Self; - - fn runtime_api<'a>(&'a self) -> sp_api::ApiRef<'a, Self::Api> { - self.clone().into() - } - } - - sp_api::mock_impl_runtime_apis! { - impl ParachainHost for MockRuntimeApi { - type Error = sp_blockchain::Error; - - fn validators(&self) -> Vec { self.validators.clone() } - fn duty_roster(&self) -> DutyRoster { self.duty_roster.clone() } - fn active_parachains() -> Vec<(ParaId, Option<(CollatorId, Retriable)>)> { vec![(ParaId::from(1), None)] } - fn global_validation_data() -> GlobalValidationData { Default::default() } - fn local_validation_data(_: ParaId) -> Option { None } - fn parachain_code(_: ParaId) -> Option { None } - fn get_heads(_: Vec<::Extrinsic>) -> Option> { - None - } - fn signing_context() -> SigningContext { - Default::default() - } - fn downward_messages(_: ParaId) -> Vec { - Vec::new() - } - } - } - - #[test] - fn launch_work_is_executed_properly() { - let executor = TaskExecutor::new(); - let keystore = keystore::Store::new_in_memory(); - - // Make sure `Bob` key is in the keystore, so this mocked node will be a parachain validator. - keystore.write().insert_ephemeral_from_seed::(&Keyring::Bob.to_seed()) - .expect("Insert key into keystore"); - - let validators = vec![ValidatorId::from(Keyring::Alice.public()), ValidatorId::from(Keyring::Bob.public())]; - let validator_duty = vec![Chain::Relay, Chain::Parachain(1.into())]; - let duty_roster = DutyRoster { validator_duty }; - - let (events_sender, events) = mpsc::unbounded(); - - let mut parachain_validation = ParachainValidationInstances { - client: Arc::new(MockRuntimeApi { validators, duty_roster }), - network: MockNetwork(events_sender.clone()), - collation_fetch: MockCollationFetch(events_sender.clone()), - spawner: executor.clone(), - availability_store: AvailabilityStore::new_in_memory(MockErasureNetworking), - live_instances: HashMap::new(), - validation_pool: None, - }; - - executor::block_on(parachain_validation.get_or_instantiate(Default::default(), &keystore, None)) - .expect("Creates new validation round"); - assert!(parachain_validation.live_instances.contains_key(&Default::default())); - - let mut events = executor::block_on_stream(events); - - assert!(matches!(events.next().unwrap(), Events::BuildTableRouter)); - assert!(matches!(events.next().unwrap(), Events::CollationFetch)); - assert!(matches!(events.next().unwrap(), Events::LocalCollation)); - - drop(events_sender); - drop(parachain_validation); - assert!(events.next().is_none()); - } - - #[test] - fn router_is_built_on_relay_chain_validator() { - let executor = TaskExecutor::new(); - let keystore = keystore::Store::new_in_memory(); - - // Make sure `Alice` key is in the keystore, so this mocked node will be a relay-chain validator. - keystore.write().insert_ephemeral_from_seed::(&Keyring::Alice.to_seed()) - .expect("Insert key into keystore"); - - let validators = vec![ValidatorId::from(Keyring::Alice.public()), ValidatorId::from(Keyring::Bob.public())]; - let validator_duty = vec![Chain::Relay, Chain::Parachain(1.into())]; - let duty_roster = DutyRoster { validator_duty }; - - let (events_sender, events) = mpsc::unbounded(); - - let mut parachain_validation = ParachainValidationInstances { - client: Arc::new(MockRuntimeApi { validators, duty_roster }), - network: MockNetwork(events_sender.clone()), - collation_fetch: MockCollationFetch(events_sender.clone()), - spawner: executor.clone(), - availability_store: AvailabilityStore::new_in_memory(MockErasureNetworking), - live_instances: HashMap::new(), - validation_pool: None, - }; - - executor::block_on(parachain_validation.get_or_instantiate(Default::default(), &keystore, None)) - .expect("Creates new validation round"); - assert!(parachain_validation.live_instances.contains_key(&Default::default())); - - let mut events = executor::block_on_stream(events); - - assert!(matches!(events.next().unwrap(), Events::BuildTableRouter)); - - drop(events_sender); - drop(parachain_validation); - assert!(events.next().is_none()); - } -}