From 49bc5d9a2c9e1275548def69e15456490894ccd9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 26 Jul 2018 16:39:20 +0200 Subject: [PATCH 01/10] begin availability store --- Cargo.toml | 1 + polkadot/availability-store/Cargo.toml | 17 ++++ polkadot/availability-store/src/lib.rs | 128 +++++++++++++++++++++++++ substrate/client/db/src/lib.rs | 2 +- 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 polkadot/availability-store/Cargo.toml create mode 100644 polkadot/availability-store/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index ffd639ca88197..e3afda77afbf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } [workspace] members = [ "polkadot/api", + "polkadot/availability-store", "polkadot/cli", "polkadot/collator", "polkadot/consensus", diff --git a/polkadot/availability-store/Cargo.toml b/polkadot/availability-store/Cargo.toml new file mode 100644 index 0000000000000..5404cc415ff28 --- /dev/null +++ b/polkadot/availability-store/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "polkadot-availability-store" +description = "Persistent database for parachain data" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +polkadot-primitives = { path = "../primitives" } +parking_lot = "0.4" +log = "0.3" +substrate-codec = { path = "../../substrate/codec" } +substrate-primitives = { path = "../../substrate/primitives" } +kvdb = { git = "https://github.com/paritytech/parity.git" } +kvdb-rocksdb = { git = "https://github.com/paritytech/parity.git" } + +[dev-dependencies] +kvdb-memorydb = { git = "https://github.com/paritytech/parity.git" } diff --git a/polkadot/availability-store/src/lib.rs b/polkadot/availability-store/src/lib.rs new file mode 100644 index 0000000000000..8f37f250a661a --- /dev/null +++ b/polkadot/availability-store/src/lib.rs @@ -0,0 +1,128 @@ +// Copyright 2018 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. + +extern crate polkadot_primitives; +extern crate parking_lot; +extern crate substrate_codec as codec; +extern crate substrate_primitives; +extern crate kvdb; +extern crate kvdb_rocksdb; + +#[macro_use] +extern crate log; + +#[cfg(test)] +extern crate kvdb_memorydb; + +use codec::{Encode, Decode}; +use kvdb::{KeyValueDB, DBTransaction}; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use polkadot_primitives::Hash; +use polkadot_primitives::parachain::{Id as ParaId, BlockData, Extrinsic}; + +use std::sync::Arc; +use std::io; + +mod columns { + pub const DATA: Option = Some(0); + pub const EXTRINSIC: Option = Some(1); + pub const NUM_COLUMNS: u32 = 2; +} + +/// 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, +} + +/// A key for extrinsic data -- relay chain parent hash and parachain ID. +pub struct Key(pub Hash, pub ParaId); + +/// Handle to the availability store. +#[derive(Clone)] +pub struct AvStore { + inner: Arc, +} + +impl AvStore { + /// Create a new `AvStore` with given config. + pub fn new(config: Config) -> io::Result { + let mut db_config = DatabaseConfig::with_columns(Some(columns::NUM_COLUMNS)); + db_config.memory_budget = config.cache_size; + db_config.wal = true; + + 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(AvStore { + inner: Arc::new(db), + }) + } + + /// Make some data available. + pub fn make_available(&self, key: Key, block_data: BlockData, extrinsic: Option) -> io::Result<()> { + let mut tx = DBTransaction::new(); + let encoded_key = (key.0, key.1).encode(); + + if let Some(extrinsic) = extrinsic { + tx.put(columns::EXTRINSIC, encoded_key.clone(), extrinsic.encode()); + } + + tx.put(columns::DATA, encoded_key, block_data.encode()); + + self.inner.write(tx) + } + + /// Query block data. + pub fn block_data(&self, key: Key) -> Option { + let encoded_key = (key.0, key.1).encode(); + match self.inner.get(columns::DATA, &key[..]) { + Ok(Some(raw)) => BlockData::decode(&mut raw[..]) + .expect("all stored data serialized correctly; qed"), + Ok(None) => None, + Err(e) => { + warn!(target: "availability", "Error reading from availability store: {:?}", e); + None + } + } + } + + /// Query extrinsic data. + pub fn extrinsic(&self, key: Key) -> Option { + let encoded_key = (key.0, key.1).encode(); + match self.inner.get(columns::EXTRINSIC, &key[..]) { + Ok(Some(raw)) => Extrinsic::decode(&mut raw[..]) + .expect("all stored data serialized correctly; qed"), + Ok(None) => None, + Err(e) => { + warn!(target: "availability", "Error reading from availability store: {:?}", e); + None + } + } + } +} + +#[cfg(test)] +mod tests { +} diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 741cd5cc9408f..5ecfbea660d1c 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -444,7 +444,7 @@ impl client::backend::Backend for Backend { } } -impl client::backend::LocalBackend for Backend +impl client::backend::LocalBackend for Backend {} #[cfg(test)] From 81933fc19eb6d3cb700e83a727e8fea6bb9312de Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 30 Jul 2018 18:45:30 +0200 Subject: [PATCH 02/10] ensure availability before issuing statements --- Cargo.lock | 15 ++ polkadot/availability-store/Cargo.toml | 2 - polkadot/availability-store/src/lib.rs | 60 +++++--- polkadot/consensus/Cargo.toml | 1 + polkadot/consensus/src/lib.rs | 6 +- polkadot/consensus/src/service.rs | 2 + polkadot/consensus/src/shared_table/mod.rs | 153 ++++++++++++++++++--- 7 files changed, 199 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17895bf28deb0..875bef2b49fa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1817,6 +1817,20 @@ dependencies = [ "substrate-state-machine 0.1.0", ] +[[package]] +name = "polkadot-availability-store" +version = "0.1.0" +dependencies = [ + "kvdb 0.1.0 (git+https://github.com/paritytech/parity.git)", + "kvdb-memorydb 0.1.0 (git+https://github.com/paritytech/parity.git)", + "kvdb-rocksdb 0.1.0 (git+https://github.com/paritytech/parity.git)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "polkadot-primitives 0.1.0", + "substrate-codec 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "polkadot-cli" version = "0.3.0" @@ -1876,6 +1890,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-api 0.1.0", + "polkadot-availability-store 0.1.0", "polkadot-parachain 0.1.0", "polkadot-primitives 0.1.0", "polkadot-runtime 0.1.0", diff --git a/polkadot/availability-store/Cargo.toml b/polkadot/availability-store/Cargo.toml index 5404cc415ff28..9a926310b04b4 100644 --- a/polkadot/availability-store/Cargo.toml +++ b/polkadot/availability-store/Cargo.toml @@ -12,6 +12,4 @@ substrate-codec = { path = "../../substrate/codec" } substrate-primitives = { path = "../../substrate/primitives" } kvdb = { git = "https://github.com/paritytech/parity.git" } kvdb-rocksdb = { git = "https://github.com/paritytech/parity.git" } - -[dev-dependencies] kvdb-memorydb = { git = "https://github.com/paritytech/parity.git" } diff --git a/polkadot/availability-store/src/lib.rs b/polkadot/availability-store/src/lib.rs index 8f37f250a661a..842f6c42e982f 100644 --- a/polkadot/availability-store/src/lib.rs +++ b/polkadot/availability-store/src/lib.rs @@ -22,12 +22,11 @@ extern crate substrate_codec as codec; extern crate substrate_primitives; extern crate kvdb; extern crate kvdb_rocksdb; +extern crate kvdb_memorydb; #[macro_use] extern crate log; -#[cfg(test)] -extern crate kvdb_memorydb; use codec::{Encode, Decode}; use kvdb::{KeyValueDB, DBTransaction}; @@ -35,6 +34,7 @@ use kvdb_rocksdb::{Database, DatabaseConfig}; use polkadot_primitives::Hash; use polkadot_primitives::parachain::{Id as ParaId, BlockData, Extrinsic}; +use std::path::PathBuf; use std::sync::Arc; use std::io; @@ -55,14 +55,28 @@ pub struct Config { /// A key for extrinsic data -- relay chain parent hash and parachain ID. pub struct Key(pub Hash, pub ParaId); +fn extract_io_err(err: ::kvdb::Error) -> io::Error { + match err { + ::kvdb::Error(::kvdb::ErrorKind::Io(io_err), _) => io_err, + ::kvdb::Error(::kvdb::ErrorKind::Msg(msg), _) => io::Error::new( + io::ErrorKind::Other, + msg, + ), + x => io::Error::new( + io::ErrorKind::Other, + format!("Unexpected error variant: {:?}", x), // only necessary because of nonexaustive match. + ) + } +} + /// Handle to the availability store. #[derive(Clone)] -pub struct AvStore { +pub struct Store { inner: Arc, } -impl AvStore { - /// Create a new `AvStore` with given config. +impl Store { + /// Create a new `Store` with given config on disk. pub fn new(config: Config) -> io::Result { let mut db_config = DatabaseConfig::with_columns(Some(columns::NUM_COLUMNS)); db_config.memory_budget = config.cache_size; @@ -70,36 +84,44 @@ impl AvStore { let path = config.path.to_str().ok_or_else(|| io::Error::new( io::ErrorKind::Other, - format!("Bad database path: {}", config.path), - )); + format!("Bad database path: {:?}", config.path), + ))?; - let db = Database::open(&db_config, &path)?; + let db = Database::open(&db_config, &path).map_err(extract_io_err)?; - Ok(AvStore { + Ok(Store { inner: Arc::new(db), }) } + /// Create a new `Store` in-memory. Useful for tests. + pub fn new_in_memory() -> Self { + Store { + inner: Arc::new(::kvdb_memorydb::create(::columns::NUM_COLUMNS)), + } + } + /// Make some data available. pub fn make_available(&self, key: Key, block_data: BlockData, extrinsic: Option) -> io::Result<()> { let mut tx = DBTransaction::new(); let encoded_key = (key.0, key.1).encode(); - if let Some(extrinsic) = extrinsic { - tx.put(columns::EXTRINSIC, encoded_key.clone(), extrinsic.encode()); + if let Some(_extrinsic) = extrinsic { + tx.put_vec(columns::EXTRINSIC, &*encoded_key, Vec::new()); } - tx.put(columns::DATA, encoded_key, block_data.encode()); + tx.put_vec(columns::DATA, &encoded_key, block_data.encode()); - self.inner.write(tx) + self.inner.write(tx).map_err(extract_io_err) } /// Query block data. pub fn block_data(&self, key: Key) -> Option { let encoded_key = (key.0, key.1).encode(); - match self.inner.get(columns::DATA, &key[..]) { - Ok(Some(raw)) => BlockData::decode(&mut raw[..]) - .expect("all stored data serialized correctly; qed"), + match self.inner.get(columns::DATA, &encoded_key[..]) { + Ok(Some(raw)) => Some( + BlockData::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed") + ), Ok(None) => None, Err(e) => { warn!(target: "availability", "Error reading from availability store: {:?}", e); @@ -111,9 +133,8 @@ impl AvStore { /// Query extrinsic data. pub fn extrinsic(&self, key: Key) -> Option { let encoded_key = (key.0, key.1).encode(); - match self.inner.get(columns::EXTRINSIC, &key[..]) { - Ok(Some(raw)) => Extrinsic::decode(&mut raw[..]) - .expect("all stored data serialized correctly; qed"), + match self.inner.get(columns::EXTRINSIC, &encoded_key[..]) { + Ok(Some(_raw)) => Some(Extrinsic), Ok(None) => None, Err(e) => { warn!(target: "availability", "Error reading from availability store: {:?}", e); @@ -125,4 +146,5 @@ impl AvStore { #[cfg(test)] mod tests { + } diff --git a/polkadot/consensus/Cargo.toml b/polkadot/consensus/Cargo.toml index 68ad8265518e4..11f85499bbf1b 100644 --- a/polkadot/consensus/Cargo.toml +++ b/polkadot/consensus/Cargo.toml @@ -13,6 +13,7 @@ log = "0.3" exit-future = "0.1" rhododendron = "0.2" polkadot-api = { path = "../api" } +polkadot-availability-store = { path = "../availability-store" } polkadot-parachain = { path = "../parachain" } polkadot-primitives = { path = "../primitives" } polkadot-runtime = { path = "../runtime" } diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 4aee785def5b2..5bc4b72cc0484 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -32,6 +32,7 @@ extern crate ed25519; extern crate parking_lot; extern crate polkadot_api; +extern crate polkadot_availability_store as extrinsic_store; extern crate polkadot_statement_table as table; extern crate polkadot_parachain as parachain; extern crate polkadot_transaction_pool as transaction_pool; @@ -66,6 +67,7 @@ use std::sync::Arc; use std::time::{Duration, Instant}; use codec::{Decode, Encode}; +use extrinsic_store::Store as ExtrinsicStore; use polkadot_api::PolkadotApi; use polkadot_primitives::{Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey}; use polkadot_primitives::parachain::{Id as ParaId, Chain, DutyRoster, BlockData, Extrinsic as ParachainExtrinsic, CandidateReceipt, CandidateSignature}; @@ -236,6 +238,8 @@ pub struct ProposerFactory { pub handle: TaskExecutor, /// The duration after which parachain-empty blocks will be allowed. pub parachain_empty_duration: Duration, + /// Store for extrinsic data. + pub extrinsic_store: ExtrinsicStore, } impl bft::Environment for ProposerFactory @@ -276,7 +280,7 @@ impl bft::Environment for ProposerFactory let active_parachains = self.client.active_parachains(&id)?; let n_parachains = active_parachains.len(); - let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash)); + let table = Arc::new(SharedTable::new(group_info, sign_with.clone(), parent_hash, self.extrinsic_store.clone())); let (router, input, output) = self.network.communication_for( authorities, table.clone(), diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index c577a98e96979..73906c8015198 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -84,6 +84,7 @@ impl Service { thread_pool: ThreadPoolHandle, parachain_empty_duration: Duration, key: ed25519::Pair, + extrinsic_store: ::extrinsic_store::Store, ) -> Service where A: LocalPolkadotApi + Send + Sync + 'static, @@ -104,6 +105,7 @@ impl Service { network, parachain_empty_duration, handle: thread_pool, + extrinsic_store, }; let bft_service = Arc::new(BftService::new(client.clone(), key, factory)); diff --git a/polkadot/consensus/src/shared_table/mod.rs b/polkadot/consensus/src/shared_table/mod.rs index 21919dd4a4869..bceef180e0c16 100644 --- a/polkadot/consensus/src/shared_table/mod.rs +++ b/polkadot/consensus/src/shared_table/mod.rs @@ -20,6 +20,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use extrinsic_store::{Key, Store as ExtrinsicStore}; use table::{self, Table, Context as TableContextTrait}; use polkadot_primitives::{Hash, SessionKey}; use polkadot_primitives::parachain::{Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt}; @@ -82,6 +83,7 @@ struct SharedTableInner { checked_validity: HashSet, checked_availability: HashSet, trackers: Vec, + extrinsic_store: ExtrinsicStore, } impl SharedTableInner { @@ -152,6 +154,8 @@ impl SharedTableInner { work.map(|work| StatementProducer { produced_statements: Default::default(), + extrinsic_store: self.extrinsic_store.clone(), + relay_parent: context.parent_hash.clone(), work }) } @@ -185,6 +189,8 @@ pub struct ProducedStatements { pub struct StatementProducer { produced_statements: ProducedStatements, work: Work, + relay_parent: Hash, + extrinsic_store: ExtrinsicStore, } impl StatementProducer { @@ -219,15 +225,17 @@ impl Future for PrimedStatementProducer D: Future, E: Future, C: FnMut(Collation) -> Option, + Err: From<::std::io::Error>, { type Item = ProducedStatements; type Error = Err; fn poll(&mut self) -> Poll { let work = &mut self.inner.work; + let statements = &mut self.inner.produced_statements; if let Async::Ready(block_data) = work.fetch_block_data.poll()? { - self.inner.produced_statements.block_data = Some(block_data.clone()); + statements.block_data = Some(block_data.clone()); if work.evaluate { let is_good = (self.check_candidate)(Collation { block_data, @@ -235,7 +243,7 @@ impl Future for PrimedStatementProducer }); let hash = work.candidate_receipt.hash(); - self.inner.produced_statements.validity = match is_good { + statements.validity = match is_good { Some(true) => Some(GenericStatement::Valid(hash)), Some(false) => Some(GenericStatement::Invalid(hash)), None => None, @@ -245,25 +253,35 @@ impl Future for PrimedStatementProducer if let Some(ref mut fetch_extrinsic) = work.fetch_extrinsic { if let Async::Ready(extrinsic) = fetch_extrinsic.poll()? { - self.inner.produced_statements.extrinsic = Some(extrinsic); + statements.extrinsic = Some(extrinsic); } } - let done = self.inner.produced_statements.block_data.is_some() && { - if work.evaluate { - true - } else if self.inner.produced_statements.extrinsic.is_some() { - self.inner.produced_statements.availability = - Some(GenericStatement::Available(work.candidate_receipt.hash())); + let done = match (&statements.block_data, &statements.extrinsic) { + (&Some(ref block), &Some(ref extrinsic)) => { + let key = Key(self.inner.relay_parent, work.candidate_receipt.parachain_index); + self.inner.extrinsic_store.make_available( + key, + block.clone(), + Some(extrinsic.clone()) + )?; + + statements.availability = Some(GenericStatement::Available( + work.candidate_receipt.hash(), + )); true - } else { - false } + (&Some(ref block), None) => { + let key = Key(self.inner.relay_parent, work.candidate_receipt.parachain_index); + self.inner.extrinsic_store.make_available(key, block.clone(), None)?; + true + } + (&None, _) => false, }; if done { - Ok(Async::Ready(::std::mem::replace(&mut self.inner.produced_statements, Default::default()))) + Ok(Async::Ready(::std::mem::replace(statements, Default::default()))) } else { Ok(Async::NotReady) } @@ -290,7 +308,12 @@ impl SharedTable { /// /// Provide the key to sign with, and the parent hash of the relay chain /// block being built. - pub fn new(groups: HashMap, key: Arc<::ed25519::Pair>, parent_hash: Hash) -> Self { + pub fn new( + groups: HashMap, + key: Arc<::ed25519::Pair>, + parent_hash: Hash, + extrinsic_store: ExtrinsicStore, + ) -> Self { SharedTable { context: Arc::new(TableContext { groups, key, parent_hash }), inner: Arc::new(Mutex::new(SharedTableInner { @@ -299,6 +322,7 @@ impl SharedTable { checked_validity: HashSet::new(), checked_availability: HashSet::new(), trackers: Vec::new(), + extrinsic_store, })) } } @@ -430,9 +454,9 @@ mod tests { #[derive(Clone)] struct DummyRouter; impl TableRouter for DummyRouter { - type Error = (); - type FetchCandidate = ::futures::future::Empty; - type FetchExtrinsic = ::futures::future::Empty; + type Error = ::std::io::Error; + type FetchCandidate = ::futures::future::Empty; + type FetchExtrinsic = ::futures::future::Empty; fn local_candidate(&self, _candidate: CandidateReceipt, _block_data: BlockData, _extrinsic: Extrinsic) { @@ -464,7 +488,12 @@ mod tests { needed_availability: 0, }); - let shared_table = SharedTable::new(groups, local_key.clone(), parent_hash); + let shared_table = SharedTable::new( + groups, + local_key.clone(), + parent_hash, + ExtrinsicStore::new_in_memory(), + ); let candidate = CandidateReceipt { parachain_index: para_id, @@ -514,7 +543,12 @@ mod tests { needed_availability: 1, }); - let shared_table = SharedTable::new(groups, local_key.clone(), parent_hash); + let shared_table = SharedTable::new( + groups, + local_key.clone(), + parent_hash, + ExtrinsicStore::new_in_memory(), + ); let candidate = CandidateReceipt { parachain_index: para_id, @@ -544,4 +578,87 @@ mod tests { assert!(producer.work.fetch_extrinsic.is_some(), "should fetch extrinsic when guaranteeing availability"); assert!(!producer.work.evaluate, "should not evaluate validity"); } + + #[test] + fn evaluate_makes_block_data_available() { + let store = ExtrinsicStore::new_in_memory(); + let relay_parent = [0; 32].into(); + let para_id = 5.into(); + let block_data = BlockData(vec![1, 2, 3]); + + let candidate = CandidateReceipt { + parachain_index: para_id, + collator: [1; 32].into(), + signature: Default::default(), + head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + balance_uploads: Vec::new(), + egress_queue_roots: Vec::new(), + fees: 1_000_000, + block_data_hash: [2; 32].into(), + }; + + let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); + let producer: StatementProducer<_, future::Empty<_, _>> = StatementProducer { + produced_statements: Default::default(), + work: Work { + candidate_receipt: candidate, + fetch_block_data: block_data_res.into_future().fuse(), + fetch_extrinsic: None, + evaluate: true, + }, + relay_parent, + extrinsic_store: store.clone(), + }; + + let produced = producer.prime(|_| Some(true)).wait().unwrap(); + + assert_eq!(produced.block_data.as_ref(), Some(&block_data)); + assert!(produced.validity.is_some()); + assert!(produced.availability.is_none()); + + assert_eq!(store.block_data(Key(relay_parent, para_id)).unwrap(), block_data); + assert!(store.extrinsic(Key(relay_parent, para_id)).is_none()); + } + + #[test] + fn full_availability() { + let store = ExtrinsicStore::new_in_memory(); + let relay_parent = [0; 32].into(); + let para_id = 5.into(); + let block_data = BlockData(vec![1, 2, 3]); + + let candidate = CandidateReceipt { + parachain_index: para_id, + collator: [1; 32].into(), + signature: Default::default(), + head_data: ::polkadot_primitives::parachain::HeadData(vec![1, 2, 3, 4]), + balance_uploads: Vec::new(), + egress_queue_roots: Vec::new(), + fees: 1_000_000, + block_data_hash: [2; 32].into(), + }; + + let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); + let extrinsic_res: ::std::io::Result<_> = Ok(Extrinsic); + let producer = StatementProducer { + produced_statements: Default::default(), + work: Work { + candidate_receipt: candidate, + fetch_block_data: block_data_res.into_future().fuse(), + fetch_extrinsic: Some(extrinsic_res.into_future().fuse()), + evaluate: false, + }, + relay_parent, + extrinsic_store: store.clone(), + }; + + let produced = producer.prime(|_| Some(true)).wait().unwrap(); + + assert_eq!(produced.block_data.as_ref(), Some(&block_data)); + assert!(produced.validity.is_none()); + assert!(produced.availability.is_some()); + + assert_eq!(store.block_data(Key(relay_parent, para_id)).unwrap(), block_data); + assert!(store.extrinsic(Key(relay_parent, para_id)).is_some()); + } } From b9b8d68d2cd08c9fdba45092e43e0c47e0b22871 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 31 Jul 2018 13:17:13 +0200 Subject: [PATCH 03/10] use io::Error in table router --- polkadot/network/src/router.rs | 55 +++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/polkadot/network/src/router.rs b/polkadot/network/src/router.rs index ff90502a31a24..8cc97beecd178 100644 --- a/polkadot/network/src/router.rs +++ b/polkadot/network/src/router.rs @@ -32,6 +32,7 @@ use tokio::runtime::TaskExecutor; use parking_lot::Mutex; use std::collections::{HashMap, HashSet}; +use std::io; use std::sync::Arc; use super::{NetworkService, Knowledge}; @@ -133,8 +134,8 @@ impl Router

{ } fn dispatch_work(&self, candidate_hash: Hash, producer: StatementProducer, parachain: ParaId) where - D: Future + Send + 'static, - E: Future + Send + 'static, + D: Future + Send + 'static, + E: Future + Send + 'static, { let parent_hash = self.parent_hash.clone(); @@ -154,28 +155,34 @@ impl Router

{ let network = self.network.clone(); let knowledge = self.knowledge.clone(); - let work = producer.prime(validate).map(move |produced| { - // store the data before broadcasting statements, so other peers can fetch. - knowledge.lock().note_candidate(candidate_hash, produced.block_data, produced.extrinsic); - - // propagate the statements - if let Some(validity) = produced.validity { - let signed = table.sign_and_import(validity.clone()); - route_statement(&*network, &*table, parachain, parent_hash, signed); - } + let work = producer.prime(validate) + .map(move |produced| { + // store the data before broadcasting statements, so other peers can fetch. + knowledge.lock().note_candidate( + candidate_hash, + produced.block_data, + produced.extrinsic + ); + + // propagate the statements + if let Some(validity) = produced.validity { + let signed = table.sign_and_import(validity.clone()); + route_statement(&*network, &*table, parachain, parent_hash, signed); + } - if let Some(availability) = produced.availability { - let signed = table.sign_and_import(availability); - route_statement(&*network, &*table, parachain, parent_hash, signed); - } - }); + if let Some(availability) = produced.availability { + let signed = table.sign_and_import(availability); + route_statement(&*network, &*table, parachain, parent_hash, signed); + } + }) + .map_err(|e| debug!(target: "p_net", "Failed to produce statements: {:?}", e)); self.task_executor.spawn(work); } } impl TableRouter for Router

{ - type Error = (); + type Error = io::Error; type FetchCandidate = BlockDataReceiver; type FetchExtrinsic = Result; @@ -207,12 +214,18 @@ pub struct BlockDataReceiver { impl Future for BlockDataReceiver { type Item = BlockData; - type Error = (); + type Error = io::Error; - fn poll(&mut self) -> Poll { + fn poll(&mut self) -> Poll { match self.inner { - Some(ref mut inner) => inner.poll().map_err(|_| ()), - None => return Err(()), + Some(ref mut inner) => inner.poll().map_err(|_| io::Error::new( + io::ErrorKind::Other, + "Sending end of channel hung up", + )), + None => return Err(io::Error::new( + io::ErrorKind::Other, + "Network service is unavailable", + )), } } } From 00db5210841990f46f2538395251f306f86e0a90 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 31 Jul 2018 17:44:59 +0200 Subject: [PATCH 04/10] initialize av-store in service and make available in network --- Cargo.lock | 2 ++ polkadot/availability-store/src/lib.rs | 1 - polkadot/network/Cargo.toml | 1 + polkadot/network/src/lib.rs | 8 ++++++++ polkadot/service/Cargo.toml | 1 + polkadot/service/src/lib.rs | 18 ++++++++++++++++++ 6 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 875bef2b49fa1..a827f6ac194cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1924,6 +1924,7 @@ dependencies = [ "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-api 0.1.0", + "polkadot-availability-store 0.1.0", "polkadot-consensus 0.1.0", "polkadot-primitives 0.1.0", "rhododendron 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1999,6 +2000,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "polkadot-api 0.1.0", + "polkadot-availability-store 0.1.0", "polkadot-consensus 0.1.0", "polkadot-executor 0.1.0", "polkadot-network 0.1.0", diff --git a/polkadot/availability-store/src/lib.rs b/polkadot/availability-store/src/lib.rs index 842f6c42e982f..9587c37ac1610 100644 --- a/polkadot/availability-store/src/lib.rs +++ b/polkadot/availability-store/src/lib.rs @@ -27,7 +27,6 @@ extern crate kvdb_memorydb; #[macro_use] extern crate log; - use codec::{Encode, Decode}; use kvdb::{KeyValueDB, DBTransaction}; use kvdb_rocksdb::{Database, DatabaseConfig}; diff --git a/polkadot/network/Cargo.toml b/polkadot/network/Cargo.toml index 37d36ea205e50..eb861e626596b 100644 --- a/polkadot/network/Cargo.toml +++ b/polkadot/network/Cargo.toml @@ -7,6 +7,7 @@ description = "Polkadot-specific networking protocol" [dependencies] parking_lot = "0.4" polkadot-api = { path = "../api" } +polkadot-availability-store = { path = "../availability-store" } polkadot-consensus = { path = "../consensus" } polkadot-primitives = { path = "../primitives" } substrate-bft = { path = "../../substrate/bft" } diff --git a/polkadot/network/src/lib.rs b/polkadot/network/src/lib.rs index 8fbc6dba3a7b5..aa9f50c1d9fb2 100644 --- a/polkadot/network/src/lib.rs +++ b/polkadot/network/src/lib.rs @@ -26,6 +26,7 @@ extern crate substrate_network; extern crate substrate_primitives; extern crate polkadot_api; +extern crate polkadot_availability_store as av_store; extern crate polkadot_consensus; extern crate polkadot_primitives; @@ -261,6 +262,7 @@ pub struct PolkadotProtocol { live_consensus: Option, in_flight: HashMap<(RequestId, NodeIndex), BlockDataRequest>, pending: Vec, + extrinsic_store: Option<::av_store::Store>, next_req_id: u64, } @@ -277,6 +279,7 @@ impl PolkadotProtocol { live_consensus: None, in_flight: HashMap::new(), pending: Vec::new(), + extrinsic_store: None, next_req_id: 1, } } @@ -680,4 +683,9 @@ impl PolkadotProtocol { } } } + + /// register availability store. + pub fn register_availability_store(&mut self, extrinsic_store: ::av_store::Store) { + self.extrinsic_store = Some(extrinsic_store); + } } diff --git a/polkadot/service/Cargo.toml b/polkadot/service/Cargo.toml index 77c7589c91df9..b0561122e1593 100644 --- a/polkadot/service/Cargo.toml +++ b/polkadot/service/Cargo.toml @@ -12,6 +12,7 @@ slog = "^2" tokio = "0.1.7" hex-literal = "0.1" ed25519 = { path = "../../substrate/ed25519" } +polkadot-availability-store = { path = "../availability-store" } polkadot-primitives = { path = "../primitives" } polkadot-runtime = { path = "../runtime" } polkadot-consensus = { path = "../consensus" } diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 6d2c4aee157b8..cba3b8e48d30b 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -19,6 +19,7 @@ //! Polkadot service. Specialized wrapper over substrate service. extern crate ed25519; +extern crate polkadot_availability_store as av_store; extern crate polkadot_primitives; extern crate polkadot_runtime; extern crate polkadot_executor; @@ -195,8 +196,22 @@ pub fn new_light(config: Configuration, executor: TaskExecutor) pub fn new_full(config: Configuration, executor: TaskExecutor) -> Result>, Error> { + // open availability store. + let av_store = { + use std::path::PathBuf; + + let mut path = PathBuf::from(config.database_path.clone()); + path.push("availability"); + + ::av_store::Store::new(::av_store::Config { + cache_size: None, + path, + })? + }; + let is_validator = (config.roles & Roles::AUTHORITY) == Roles::AUTHORITY; let service = service::Service::>::new(config, executor.clone())?; + // Spin consensus service if configured let consensus = if is_validator { // Load the first available key @@ -214,11 +229,14 @@ pub fn new_full(config: Configuration, executor: TaskExecutor) executor, ::std::time::Duration::from_millis(4000), // TODO: dynamic key, + av_store.clone(), )) } else { None }; + service.network().with_spec(|spec, _| spec.register_availability_store(av_store)); + Ok(Service { client: service.client(), network: service.network(), From 4b40d77178a41ffb65807a0aa5eac2dfef939af9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 1 Aug 2018 15:17:03 +0200 Subject: [PATCH 05/10] encode borrowed values --- substrate/codec/src/codec.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/substrate/codec/src/codec.rs b/substrate/codec/src/codec.rs index beee49ba3d8a4..b18e1772440e0 100644 --- a/substrate/codec/src/codec.rs +++ b/substrate/codec/src/codec.rs @@ -294,6 +294,20 @@ impl Encode for () { } } +impl<'a, T: 'a + Encode + ?Sized> Encode for &'a T { + fn encode_to(&self, dest: &mut D) { + (&**self).encode_to(dest) + } + + fn using_encoded R>(&self, f: F) -> R { + (&**self).using_encoded(f) + } + + fn encode(&self) -> Vec { + (&**self).encode() + } +} + impl Decode for () { fn decode(_: &mut I) -> Option<()> { Some(()) @@ -477,4 +491,14 @@ mod tests { assert_eq!(slice, &b"\x0b\0\0\0Hello world") ); } + + #[test] + fn encode_borrowed_tuple() { + let x = vec![1u8, 2, 3, 4]; + let y = 128i64; + + let encoded = (&x, &y).encode(); + + assert_eq!((x, y), Decode::decode(&mut &encoded[..]).unwrap()); + } } From cd13aa22bee0e850e3e8d8dcb0a880090aed31a5 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 1 Aug 2018 15:28:20 +0200 Subject: [PATCH 06/10] finalization for availability store --- polkadot/availability-store/src/lib.rs | 137 ++++++++++++++++++--- polkadot/consensus/src/collation.rs | 5 + polkadot/consensus/src/lib.rs | 29 ++++- polkadot/consensus/src/shared_table/mod.rs | 30 +++-- 4 files changed, 172 insertions(+), 29 deletions(-) diff --git a/polkadot/availability-store/src/lib.rs b/polkadot/availability-store/src/lib.rs index 9587c37ac1610..33ace5794cfe7 100644 --- a/polkadot/availability-store/src/lib.rs +++ b/polkadot/availability-store/src/lib.rs @@ -33,13 +33,14 @@ use kvdb_rocksdb::{Database, DatabaseConfig}; use polkadot_primitives::Hash; use polkadot_primitives::parachain::{Id as ParaId, BlockData, Extrinsic}; +use std::collections::HashSet; use std::path::PathBuf; use std::sync::Arc; use std::io; mod columns { pub const DATA: Option = Some(0); - pub const EXTRINSIC: Option = Some(1); + pub const META: Option = Some(1); pub const NUM_COLUMNS: u32 = 2; } @@ -51,8 +52,19 @@ pub struct Config { pub path: PathBuf, } -/// A key for extrinsic data -- relay chain parent hash and parachain ID. -pub struct Key(pub Hash, pub ParaId); +/// Some data to keep available. +pub struct Data { + /// The relay chain parent hash this should be localized to. + pub relay_parent: Hash, + /// The parachain index for this candidate. + pub parachain_id: ParaId, + /// Unique candidate receipt hash. + pub candidate_hash: Hash, + /// Block data. + pub block_data: BlockData, + /// Extrinsic data. + pub extrinsic: Option, +} fn extract_io_err(err: ::kvdb::Error) -> io::Error { match err { @@ -68,6 +80,14 @@ fn extract_io_err(err: ::kvdb::Error) -> io::Error { } } +fn block_data_key(relay_parent: &Hash, candidate_hash: &Hash) -> Vec { + (relay_parent, candidate_hash, 0i8).encode() +} + +fn extrinsic_key(relay_parent: &Hash, candidate_hash: &Hash) -> Vec { + (relay_parent, candidate_hash, 1i8).encode() +} + /// Handle to the availability store. #[derive(Clone)] pub struct Store { @@ -100,23 +120,67 @@ impl Store { } } - /// Make some data available. - pub fn make_available(&self, key: Key, block_data: BlockData, extrinsic: Option) -> io::Result<()> { + /// Make some data available provisionally. + pub fn make_available(&self, data: Data) -> io::Result<()> { let mut tx = DBTransaction::new(); - let encoded_key = (key.0, key.1).encode(); - if let Some(_extrinsic) = extrinsic { - tx.put_vec(columns::EXTRINSIC, &*encoded_key, Vec::new()); + // note the meta key. + let mut v = match self.inner.get(columns::META, &*data.relay_parent) { + Ok(Some(raw)) => Vec::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed"), + Ok(None) => Vec::new(), + Err(e) => { + warn!(target: "availability", "Error reading from availability store: {:?}", e); + Vec::new() + } + }; + + v.push(data.candidate_hash); + tx.put_vec(columns::META, &data.relay_parent[..], v.encode()); + + tx.put_vec( + columns::DATA, + block_data_key(&data.relay_parent, &data.candidate_hash).as_slice(), + data.block_data.encode() + ); + + if let Some(_extrinsic) = data.extrinsic { + tx.put_vec( + columns::DATA, + extrinsic_key(&data.relay_parent, &data.candidate_hash).as_slice(), + vec![], + ); } - tx.put_vec(columns::DATA, &encoded_key, block_data.encode()); + self.inner.write(tx).map_err(extract_io_err) + } + + /// Note that a set of candidates have been included in a finalized block with given hash and parent hash. + pub fn candidates_finalized(&self, parent: Hash, finalized_candidates: HashSet) -> io::Result<()> { + let mut tx = DBTransaction::new(); + + let v = match self.inner.get(columns::META, &parent[..]) { + Ok(Some(raw)) => Vec::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed"), + Ok(None) => Vec::new(), + Err(e) => { + warn!(target: "availability", "Error reading from availability store: {:?}", e); + Vec::new() + } + }; + tx.delete(columns::META, &parent[..]); + + for candidate_hash in v { + if !finalized_candidates.contains(&candidate_hash) { + tx.delete(columns::DATA, block_data_key(&parent, &candidate_hash).as_slice()); + tx.delete(columns::DATA, extrinsic_key(&parent, &candidate_hash).as_slice()); + } + } self.inner.write(tx).map_err(extract_io_err) } /// Query block data. - pub fn block_data(&self, key: Key) -> Option { - let encoded_key = (key.0, key.1).encode(); + pub fn block_data(&self, relay_parent: Hash, candidate_hash: Hash) -> Option { + let encoded_key = block_data_key(&relay_parent, &candidate_hash); match self.inner.get(columns::DATA, &encoded_key[..]) { Ok(Some(raw)) => Some( BlockData::decode(&mut &raw[..]).expect("all stored data serialized correctly; qed") @@ -130,9 +194,9 @@ impl Store { } /// Query extrinsic data. - pub fn extrinsic(&self, key: Key) -> Option { - let encoded_key = (key.0, key.1).encode(); - match self.inner.get(columns::EXTRINSIC, &encoded_key[..]) { + pub fn extrinsic(&self, relay_parent: Hash, candidate_hash: Hash) -> Option { + let encoded_key = extrinsic_key(&relay_parent, &candidate_hash); + match self.inner.get(columns::DATA, &encoded_key[..]) { Ok(Some(_raw)) => Some(Extrinsic), Ok(None) => None, Err(e) => { @@ -145,5 +209,50 @@ impl Store { #[cfg(test)] mod tests { + use super::*; + + #[test] + fn finalization_removes_unneeded() { + let relay_parent = [1; 32].into(); + + let para_id_1 = 5.into(); + let para_id_2 = 6.into(); + + let candidate_1 = [2; 32].into(); + let candidate_2 = [3; 32].into(); + let block_data_1 = BlockData(vec![1, 2, 3]); + let block_data_2 = BlockData(vec![4, 5, 6]); + + let store = Store::new_in_memory(); + store.make_available(Data { + relay_parent, + parachain_id: para_id_1, + candidate_hash: candidate_1, + block_data: block_data_1.clone(), + extrinsic: Some(Extrinsic), + }).unwrap(); + + store.make_available(Data { + relay_parent, + parachain_id: para_id_2, + candidate_hash: candidate_2, + block_data: block_data_2.clone(), + extrinsic: Some(Extrinsic), + }).unwrap(); + + assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1); + assert_eq!(store.block_data(relay_parent, candidate_2).unwrap(), block_data_2); + + assert!(store.extrinsic(relay_parent, candidate_1).is_some()); + assert!(store.extrinsic(relay_parent, candidate_2).is_some()); + + store.candidates_finalized(relay_parent, [candidate_1].iter().cloned().collect()).unwrap(); + + assert_eq!(store.block_data(relay_parent, candidate_1).unwrap(), block_data_1); + assert!(store.block_data(relay_parent, candidate_2).is_none()); + + assert!(store.extrinsic(relay_parent, candidate_1).is_some()); + assert!(store.extrinsic(relay_parent, candidate_2).is_none()); + } } diff --git a/polkadot/consensus/src/collation.rs b/polkadot/consensus/src/collation.rs index f7db48db619bb..b02b49aaa6953 100644 --- a/polkadot/consensus/src/collation.rs +++ b/polkadot/consensus/src/collation.rs @@ -73,6 +73,11 @@ impl CollationFetch { live_fetch: None, } } + + /// Access the underlying relay parent hash. + pub fn relay_parent(&self) -> Hash { + self.relay_parent_hash + } } impl Future for CollationFetch { diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 5bc4b72cc0484..98d357bd8f977 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -339,19 +339,42 @@ fn dispatch_collation_work( router: R, handle: &TaskExecutor, work: Option>, + extrinsic_store: Store, ) -> exit_future::Signal where C: Collators + Send + 'static, P: PolkadotApi + Send + Sync + 'static, ::Future: Send + 'static, R: TableRouter + Send + 'static, { + use extrinsic_store::Data; + let (signal, exit) = exit_future::signal(); + + let work = match work { + Some(w) => w, + None => return signal, + }; + + let relay_parent = work.relay_parent(); let handled_work = work.then(move |result| match result { - Ok(Some((collation, extrinsic))) => { - router.local_candidate(collation.receipt, collation.block_data, extrinsic); + Ok((collation, extrinsic)) => { + let res = extrinsic_store.make_available(Data { + relay_parent, + parachain_id: collation.receipt.parachain_index, + candidate_hash: collation.receipt.hash(), + block_data: collation.block_data.clone(), + extrinsic: Some(collation.extrinsic.clone()), + }); + + match res { + Ok(()) => + router.local_candidate(collation.receipt, collation.block_data, extrinsic), + Err(e) => + warn!(target: "consensus", "Failed to make collation data available"), + } + Ok(()) } - Ok(None) => Ok(()), Err(_e) => { warn!(target: "consensus", "Failed to collate candidate"); Ok(()) diff --git a/polkadot/consensus/src/shared_table/mod.rs b/polkadot/consensus/src/shared_table/mod.rs index bceef180e0c16..49aa4e74c49dc 100644 --- a/polkadot/consensus/src/shared_table/mod.rs +++ b/polkadot/consensus/src/shared_table/mod.rs @@ -20,7 +20,7 @@ use std::collections::{HashMap, HashSet}; use std::sync::Arc; -use extrinsic_store::{Key, Store as ExtrinsicStore}; +use extrinsic_store::{Data, Store as ExtrinsicStore}; use table::{self, Table, Context as TableContextTrait}; use polkadot_primitives::{Hash, SessionKey}; use polkadot_primitives::parachain::{Id as ParaId, BlockData, Collation, Extrinsic, CandidateReceipt}; @@ -259,22 +259,28 @@ impl Future for PrimedStatementProducer let done = match (&statements.block_data, &statements.extrinsic) { (&Some(ref block), &Some(ref extrinsic)) => { - let key = Key(self.inner.relay_parent, work.candidate_receipt.parachain_index); - self.inner.extrinsic_store.make_available( - key, - block.clone(), - Some(extrinsic.clone()) - )?; + let candidate_hash = work.candidate_receipt.hash(); + self.inner.extrinsic_store.make_available(Data { + relay_parent: self.inner.relay_parent, + parachain_id: work.candidate_receipt.parachain_index, + candidate_hash, + block_data: block.clone(), + extrinsic: Some(extrinsic.clone()), + })?; - statements.availability = Some(GenericStatement::Available( - work.candidate_receipt.hash(), - )); + statements.availability = Some(GenericStatement::Available(candidate_hash)); true } (&Some(ref block), None) => { - let key = Key(self.inner.relay_parent, work.candidate_receipt.parachain_index); - self.inner.extrinsic_store.make_available(key, block.clone(), None)?; + self.inner.extrinsic_store.make_available(Data { + relay_parent: self.inner.relay_parent, + parachain_id: work.candidate_receipt.parachain_index, + candidate_hash: work.candidate_receipt.hash(), + block_data: block.clone(), + extrinsic: None, + })?; + true } (&None, _) => false, From d4a7cd0e32a1ca2759d1f1915b3359b2e92f91ac Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 1 Aug 2018 15:34:53 +0200 Subject: [PATCH 07/10] integrate fully into consensus --- polkadot/consensus/src/lib.rs | 7 ++++--- polkadot/consensus/src/shared_table/mod.rs | 12 ++++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/polkadot/consensus/src/lib.rs b/polkadot/consensus/src/lib.rs index 98d357bd8f977..6a930ce3388dd 100644 --- a/polkadot/consensus/src/lib.rs +++ b/polkadot/consensus/src/lib.rs @@ -313,6 +313,7 @@ impl bft::Environment for ProposerFactory router.clone(), &self.handle, collation_work, + self.extrinsic_store.clone(), ); let proposer = Proposer { @@ -339,7 +340,7 @@ fn dispatch_collation_work( router: R, handle: &TaskExecutor, work: Option>, - extrinsic_store: Store, + extrinsic_store: ExtrinsicStore, ) -> exit_future::Signal where C: Collators + Send + 'static, P: PolkadotApi + Send + Sync + 'static, @@ -363,14 +364,14 @@ fn dispatch_collation_work( parachain_id: collation.receipt.parachain_index, candidate_hash: collation.receipt.hash(), block_data: collation.block_data.clone(), - extrinsic: Some(collation.extrinsic.clone()), + extrinsic: Some(extrinsic.clone()), }); match res { Ok(()) => router.local_candidate(collation.receipt, collation.block_data, extrinsic), Err(e) => - warn!(target: "consensus", "Failed to make collation data available"), + warn!(target: "consensus", "Failed to make collation data available: {:?}", e), } Ok(()) diff --git a/polkadot/consensus/src/shared_table/mod.rs b/polkadot/consensus/src/shared_table/mod.rs index 49aa4e74c49dc..7adc13fb12d8e 100644 --- a/polkadot/consensus/src/shared_table/mod.rs +++ b/polkadot/consensus/src/shared_table/mod.rs @@ -603,6 +603,8 @@ mod tests { block_data_hash: [2; 32].into(), }; + let hash = candidate.hash(); + let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); let producer: StatementProducer<_, future::Empty<_, _>> = StatementProducer { produced_statements: Default::default(), @@ -622,8 +624,8 @@ mod tests { assert!(produced.validity.is_some()); assert!(produced.availability.is_none()); - assert_eq!(store.block_data(Key(relay_parent, para_id)).unwrap(), block_data); - assert!(store.extrinsic(Key(relay_parent, para_id)).is_none()); + assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data); + assert!(store.extrinsic(relay_parent, hash).is_none()); } #[test] @@ -644,6 +646,8 @@ mod tests { block_data_hash: [2; 32].into(), }; + let hash = candidate.hash(); + let block_data_res: ::std::io::Result<_> = Ok(block_data.clone()); let extrinsic_res: ::std::io::Result<_> = Ok(Extrinsic); let producer = StatementProducer { @@ -664,7 +668,7 @@ mod tests { assert!(produced.validity.is_none()); assert!(produced.availability.is_some()); - assert_eq!(store.block_data(Key(relay_parent, para_id)).unwrap(), block_data); - assert!(store.extrinsic(Key(relay_parent, para_id)).is_some()); + assert_eq!(store.block_data(relay_parent, hash).unwrap(), block_data); + assert!(store.extrinsic(relay_parent, hash).is_some()); } } From 1063d077a2ab29a1c190012d2bb2b58cd60b3ac4 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 1 Aug 2018 18:24:18 +0200 Subject: [PATCH 08/10] update WASM --- .../release/demo_runtime.compact.wasm | Bin 244053 -> 244061 bytes .../release/demo_runtime.wasm | Bin 244138 -> 244147 bytes .../release/polkadot_runtime.compact.wasm | Bin 322938 -> 322956 bytes .../release/polkadot_runtime.wasm | Bin 323027 -> 323041 bytes .../substrate_test_runtime.compact.wasm | Bin 56218 -> 56218 bytes .../release/substrate_test_runtime.wasm | Bin 56350 -> 56350 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index e202d418ffb6fe6158bea559f1322e1731f18557..fd2432257f5e2cfcfba1816abe47d880cff81357 100644 GIT binary patch delta 14909 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 diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index b684d5b09713a33a2ed4e339bfeefcab8d27dcf4..2305175c6d9d47240777e4825501092303dd2b5e 100755 GIT binary patch delta 14928 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 EANu@2iU0rr delta 14875 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 NCtvhvy^qV8@c%XI6o~); diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 74088d063a466b7525c24f58eeb55360060d15f7..831b77b45e1c1f8dcbcc7780e361380736c2f18f 100644 GIT binary patch delta 16725 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 delta 16601 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 delta 16538 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 E05zt#VgLXD diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index a8308fbcaacfb4554993b55e292fcbaee8f0c38d..d668a832eeb521532f7be3dc870c67ce1fb972c3 100644 GIT binary patch delta 424 zcmbQWoq5)F<_%XFIhVH`VBoH2a9qCm9%C>I(}cFo#%#aXK(sz@Fca^R)&mTVEDB5l z@{TK7H}~`BGJ&N230g9OXiH%xMyB&ElWj$0fYjz_5mB&ok=QD3khGlABrza8InYgb z@;}Rf$)c85pxnj(zy?5Nq&MqXPi17>GWm#2EJ%T}?MjgLbGA{;OiitmwH)VyL{B>I z0`fMuI?V*zt>B^$vJ7a%9+f7LE0qLf8MqzU9T^1#fiyx$KpV&fy3WvjIoN-7f%dw+Q*0p`gG{%L{&%mNG`0HT?I^qlZGaPI(}UK{#%#aXK(sz@Fca^`mIDlqEDB5l z@{V6yHuv-AGJ&N230g9OXiH%x5IxyeLz$wzErK?;;@SAw*kvyEbAI^Qx`%W*D9^rYi1Aa8T4 z(@e143NHE}%Ya7gQ8^ECrILUw1Ggi)Bcp&IkVXgzXal)G;|$%GgZ+2Qn}rc%&;uVa zCZ^l1lYjWRF$zz%_m^jSz%)6*KTYrflK=w<+yv50K>Eby>;C3UlRbk}6+pqk%~FzD zT*3eh4hO#=Q*AXR}vmW&{}N0^C`>3qxN2_iB;YV$e~QLyw;u~pn4=|ZJR zVnBMbomKMW49hDZAv-H!kWQeG-sVQ@sf>(UCX3s~f<($}SArBM+C?!lHMLHzb({+l z4HOcdtnaiOD6;v6(@e0X#V-0FTP81Y+oRG1a-NcaA_KQ0yCb83Fpx$F3Frg4KxehM zF9-Wk*N24>WRS717!%W^w#nZ9Zb~A|0t~_o3=ChH7#LWAG&2x`03#5y0I>k`2C;b6W6@ F4gfKChot}j delta 426 zcmbQYgL&Q#<_)hHIhVB^VBoH2a9phO#=Q*AXR}vmW&{}N0#(!Z+uUe9m65Swvbb$5NKu*XN{|9YyC`O+^DUEW9p{2X1BHYq z>pLw6ifq2&G!txTv5P*)mdQ)p_Nbf(IZsJIk%8Nh-H}m17)T?81oVMipph-^%fWus z^8~Hb+Wg=o02fI0D~|C1H)G)1_o9j%?!jKzzD=FK>UDd@@D@u!3RtN zAW?>!KpLo$;lyUG0CT3vbAnVAK!L^0Qj%I+!T=5}ZtjA_qRf&?U5HS_ Date: Thu, 2 Aug 2018 18:54:24 +0200 Subject: [PATCH 09/10] pull availability data out of the store --- polkadot/network/src/lib.rs | 25 +++++++++++----- polkadot/network/src/tests.rs | 56 +++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/polkadot/network/src/lib.rs b/polkadot/network/src/lib.rs index 6a95204f6b07b..366a2f659b923 100644 --- a/polkadot/network/src/lib.rs +++ b/polkadot/network/src/lib.rs @@ -198,7 +198,9 @@ struct CurrentConsensus { impl CurrentConsensus { // get locally stored block data for a candidate. - fn block_data(&self, hash: &Hash) -> Option { + fn block_data(&self, relay_parent: &Hash, hash: &Hash) -> Option { + if relay_parent != &self.parent_hash { return None } + self.knowledge.lock().candidates.get(hash) .and_then(|entry| entry.block_data.clone()) } @@ -212,8 +214,8 @@ pub enum Message { /// As a validator, tell the peer your current session key. // TODO: do this with a cryptographic proof of some kind SessionKey(SessionKey), - /// Requesting parachain block data by candidate hash. - RequestBlockData(RequestId, Hash), + /// Requesting parachain block data by (relay_parent, candidate_hash). + RequestBlockData(RequestId, Hash, Hash), /// Provide block data by candidate hash or nothing if unknown. BlockData(RequestId, Option), /// Tell a collator their role. @@ -234,9 +236,10 @@ impl Encode for Message { dest.push_byte(1); dest.push(k); } - Message::RequestBlockData(ref id, ref d) => { + Message::RequestBlockData(ref id, ref r, ref d) => { dest.push_byte(2); dest.push(id); + dest.push(r); dest.push(d); } Message::BlockData(ref id, ref d) => { @@ -262,7 +265,10 @@ impl Decode for Message { match input.read_byte()? { 0 => Some(Message::Statement(Decode::decode(input)?, Decode::decode(input)?)), 1 => Some(Message::SessionKey(Decode::decode(input)?)), - 2 => Some(Message::RequestBlockData(Decode::decode(input)?, Decode::decode(input)?)), + 2 => { + let x: (_, _, _) = Decode::decode(input)?; + Some(Message::RequestBlockData(x.0, x.1, x.2)) + } 3 => Some(Message::BlockData(Decode::decode(input)?, Decode::decode(input)?)), 4 => Some(Message::CollatorRole(Decode::decode(input)?)), 5 => Some(Message::Collation(Decode::decode(input)?, Decode::decode(input)?)), @@ -388,7 +394,7 @@ impl PolkadotProtocol { send_polkadot_message( ctx, who, - Message::RequestBlockData(req_id, pending.candidate_hash) + Message::RequestBlockData(req_id, pending.consensus_parent, pending.candidate_hash) ); self.in_flight.insert((req_id, who), pending); @@ -409,9 +415,12 @@ impl PolkadotProtocol { Message::Statement(parent_hash, _statement) => self.consensus_gossip.on_chain_specific(ctx, who, raw, parent_hash), Message::SessionKey(key) => self.on_session_key(ctx, who, key), - Message::RequestBlockData(req_id, hash) => { + Message::RequestBlockData(req_id, relay_parent, candidate_hash) => { let block_data = self.live_consensus.as_ref() - .and_then(|c| c.block_data(&hash)); + .and_then(|c| c.block_data(&relay_parent, &candidate_hash)) + .or_else(|| self.extrinsic_store.as_ref() + .and_then(|s| s.block_data(relay_parent, candidate_hash)) + ); send_polkadot_message(ctx, who, Message::BlockData(req_id, block_data)); } diff --git a/polkadot/network/src/tests.rs b/polkadot/network/src/tests.rs index 356d272e3f610..f5ad78dae9093 100644 --- a/polkadot/network/src/tests.rs +++ b/polkadot/network/src/tests.rs @@ -174,7 +174,7 @@ fn fetches_from_those_with_knowledge() { let mut ctx = TestContext::default(); on_message(&mut protocol, &mut ctx, peer_a, Message::SessionKey(a_key)); assert!(protocol.validators.contains_key(&a_key)); - assert!(ctx.has_message(peer_a, Message::RequestBlockData(1, candidate_hash))); + assert!(ctx.has_message(peer_a, Message::RequestBlockData(1, parent_hash, candidate_hash))); } knowledge.lock().note_statement(b_key, &GenericStatement::Valid(candidate_hash)); @@ -184,7 +184,7 @@ fn fetches_from_those_with_knowledge() { let mut ctx = TestContext::default(); protocol.on_connect(&mut ctx, peer_b, make_status(&status, Roles::AUTHORITY)); on_message(&mut protocol, &mut ctx, peer_b, Message::SessionKey(b_key)); - assert!(!ctx.has_message(peer_b, Message::RequestBlockData(2, candidate_hash))); + assert!(!ctx.has_message(peer_b, Message::RequestBlockData(2, parent_hash, candidate_hash))); } @@ -193,7 +193,7 @@ fn fetches_from_those_with_knowledge() { let mut ctx = TestContext::default(); protocol.on_disconnect(&mut ctx, peer_a); assert!(!protocol.validators.contains_key(&a_key)); - assert!(ctx.has_message(peer_b, Message::RequestBlockData(2, candidate_hash))); + assert!(ctx.has_message(peer_b, Message::RequestBlockData(2, parent_hash, candidate_hash))); } // peer B comes back with block data. @@ -205,6 +205,56 @@ fn fetches_from_those_with_knowledge() { } } +#[test] +fn fetches_available_block_data() { + let mut protocol = PolkadotProtocol::new(None); + + let peer_a = 1; + let parent_hash = [0; 32].into(); + + let block_data = BlockData(vec![1, 2, 3, 4]); + let block_data_hash = block_data.hash(); + let para_id = 5.into(); + let candidate_receipt = CandidateReceipt { + parachain_index: para_id, + collator: [255; 32].into(), + head_data: HeadData(vec![9, 9, 9]), + signature: H512::from([1; 64]).into(), + balance_uploads: Vec::new(), + egress_queue_roots: Vec::new(), + fees: 1_000_000, + block_data_hash, + }; + + let candidate_hash = candidate_receipt.hash(); + let av_store = ::av_store::Store::new_in_memory(); + + let status = Status { collating_for: None }; + + protocol.register_availability_store(av_store.clone()); + + av_store.make_available(::av_store::Data { + relay_parent: parent_hash, + parachain_id: para_id, + candidate_hash, + block_data: block_data.clone(), + extrinsic: None, + }).unwrap(); + + // connect peer A + { + let mut ctx = TestContext::default(); + protocol.on_connect(&mut ctx, peer_a, make_status(&status, Roles::FULL)); + } + + // peer A asks for historic block data and gets response + { + let mut ctx = TestContext::default(); + on_message(&mut protocol, &mut ctx, peer_a, Message::RequestBlockData(1, parent_hash, candidate_hash)); + assert!(ctx.has_message(peer_a, Message::BlockData(1, Some(block_data)))); + } +} + #[test] fn remove_bad_collator() { let mut protocol = PolkadotProtocol::new(None); From 91f98399147363f5e5481ddf03a75abbe1054db7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 3 Aug 2018 18:00:53 +0200 Subject: [PATCH 10/10] prune available data which does not need to be kept anymore --- polkadot/consensus/src/service.rs | 69 ++++++++++++++++++++++++++++--- polkadot/service/src/lib.rs | 4 +- substrate/client/src/client.rs | 17 ++++++++ substrate/client/src/lib.rs | 2 +- substrate/service/src/lib.rs | 7 ++++ 5 files changed, 91 insertions(+), 8 deletions(-) diff --git a/polkadot/consensus/src/service.rs b/polkadot/consensus/src/service.rs index f72435c2a58d0..3b4e7376942c2 100644 --- a/polkadot/consensus/src/service.rs +++ b/polkadot/consensus/src/service.rs @@ -28,12 +28,13 @@ use std::time::{Duration, Instant}; use std::sync::Arc; use bft::{self, BftService}; -use client::{BlockchainEvents, ChainHead}; +use client::{BlockchainEvents, ChainHead, BlockBody}; use ed25519; use futures::prelude::*; use polkadot_api::LocalPolkadotApi; use polkadot_primitives::{Block, Header}; use transaction_pool::TransactionPool; +use extrinsic_store::Store as ExtrinsicStore; use tokio::executor::current_thread::TaskExecutor as LocalThreadHandle; use tokio::runtime::TaskExecutor as ThreadPoolHandle; @@ -68,6 +69,56 @@ fn start_bft( } } +// creates a task to prune redundant entries in availability store upon block finalization +// +// NOTE: this will need to be changed to finality notification rather than +// block import notifications when the consensus switches to non-instant finality. +fn prune_unneeded_availability(client: Arc, extrinsic_store: ExtrinsicStore) + -> impl Future + Send + where C: Send + Sync + BlockchainEvents + BlockBody + 'static +{ + use codec::{Encode, Decode}; + use polkadot_primitives::BlockId; + use polkadot_runtime::CheckedBlock; + + enum NotifyError { + NoBody(::client::error::Error), + UnexpectedFormat, + ExtrinsicsWrong, + } + + impl NotifyError { + fn log(&self, hash: &::polkadot_primitives::Hash) { + match *self { + NotifyError::NoBody(ref err) => warn!("Failed to fetch block body for imported block {:?}: {:?}", hash, err), + NotifyError::UnexpectedFormat => warn!("Consensus outdated: Block {:?} has unexpected body format", hash), + NotifyError::ExtrinsicsWrong => warn!("Consensus outdated: Failed to fetch block body for imported block {:?}", hash), + } + } + } + + client.import_notification_stream() + .for_each(move |notification| { + let checked_block = client.block_body(&BlockId::hash(notification.hash)) + .map_err(NotifyError::NoBody) + .map(|b| ::polkadot_runtime::Block::decode(&mut b.encode().as_slice())) + .and_then(|maybe_block| maybe_block.ok_or(NotifyError::UnexpectedFormat)) + .and_then(|block| CheckedBlock::new(block).map_err(|_| NotifyError::ExtrinsicsWrong)); + + match checked_block { + Ok(block) => { + let candidate_hashes = block.parachain_heads().iter().map(|c| c.hash()).collect(); + if let Err(e) = extrinsic_store.candidates_finalized(notification.header.parent_hash, candidate_hashes) { + warn!(target: "consensus", "Failed to prune unneeded available data: {:?}", e); + } + } + Err(e) => e.log(¬ification.hash) + } + + Ok(()) + }) +} + /// Consensus service. Starts working when created. pub struct Service { thread: Option>, @@ -84,11 +135,11 @@ impl Service { thread_pool: ThreadPoolHandle, parachain_empty_duration: Duration, key: ed25519::Pair, - extrinsic_store: ::extrinsic_store::Store, + extrinsic_store: ExtrinsicStore, ) -> Service where A: LocalPolkadotApi + Send + Sync + 'static, - C: BlockchainEvents + ChainHead + bft::BlockImport + bft::Authorities + Send + Sync + 'static, + C: BlockchainEvents + ChainHead + BlockBody + bft::BlockImport + bft::Authorities + Send + Sync + 'static, N: Network + Collators + Send + 'static, N::TableRouter: Send + 'static, ::Future: Send + 'static, @@ -104,8 +155,8 @@ impl Service { collators: network.clone(), network, parachain_empty_duration, - handle: thread_pool, - extrinsic_store, + handle: thread_pool.clone(), + extrinsic_store: extrinsic_store.clone(), }; let bft_service = Arc::new(BftService::new(client.clone(), key, factory)); @@ -153,6 +204,14 @@ impl Service { runtime.spawn(notifications); runtime.spawn(timed); + + let prune_available = prune_unneeded_availability(client, extrinsic_store) + .select(exit.clone()) + .then(|_| Ok(())); + + // spawn this on the tokio executor since it's fine on a thread pool. + thread_pool.spawn(prune_available); + if let Err(e) = runtime.block_on(exit) { debug!("BFT event loop error {:?}", e); } diff --git a/polkadot/service/src/lib.rs b/polkadot/service/src/lib.rs index 61e36263ca9f6..3814a1c98da38 100644 --- a/polkadot/service/src/lib.rs +++ b/polkadot/service/src/lib.rs @@ -44,7 +44,7 @@ pub mod chain_spec; use std::sync::Arc; use std::collections::HashMap; -use codec::Encode; +use codec::{Encode, Decode}; use transaction_pool::TransactionPool; use polkadot_api::{PolkadotApi, light::RemotePolkadotApiWrapper}; use polkadot_primitives::{parachain, AccountId, Block, BlockId, Hash}; @@ -313,7 +313,7 @@ impl network::TransactionPool for TransactionPoolAdapter Some(*xt.hash()), diff --git a/substrate/client/src/client.rs b/substrate/client/src/client.rs index bd7f3c5581a2e..6d52ed470b971 100644 --- a/substrate/client/src/client.rs +++ b/substrate/client/src/client.rs @@ -65,6 +65,12 @@ pub trait ChainHead { fn best_block_header(&self) -> Result<::Header, error::Error>; } +/// Fetch block body by ID. +pub trait BlockBody { + /// Get block body by ID. Returns `None` if the body is not stored. + fn block_body(&self, id: &BlockId) -> error::Result::Extrinsic>>>; +} + /// Client info // TODO: split queue info from chain info and amalgamate into single struct. #[derive(Debug)] @@ -560,6 +566,17 @@ impl ChainHead for Client } } +impl BlockBody for Client + where + B: backend::Backend, + E: CallExecutor, + Block: BlockT, +{ + fn block_body(&self, id: &BlockId) -> error::Result::Extrinsic>>> { + self.body(id) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/client/src/lib.rs b/substrate/client/src/lib.rs index 8a4a143914a96..0032fc64e4f03 100644 --- a/substrate/client/src/lib.rs +++ b/substrate/client/src/lib.rs @@ -57,7 +57,7 @@ pub use blockchain::Info as ChainInfo; pub use call_executor::{CallResult, CallExecutor, LocalCallExecutor}; pub use client::{ new_in_mem, - BlockStatus, BlockOrigin, BlockchainEventStream, BlockchainEvents, + BlockBody, BlockStatus, BlockOrigin, BlockchainEventStream, BlockchainEvents, Client, ClientInfo, ChainHead, ImportResult, JustifiedHeader, }; diff --git a/substrate/service/src/lib.rs b/substrate/service/src/lib.rs index d906bae088e8d..9ac3dd7c043d9 100644 --- a/substrate/service/src/lib.rs +++ b/substrate/service/src/lib.rs @@ -86,6 +86,7 @@ pub struct Service { network: Arc>, extrinsic_pool: Arc, keystore: Keystore, + exit: ::exit_future::Exit, signal: Option, _rpc_http: Option, _rpc_ws: Option, @@ -246,6 +247,7 @@ impl Service extrinsic_pool: extrinsic_pool, signal: Some(signal), keystore: keystore, + exit, _rpc_http: rpc_http, _rpc_ws: rpc_ws, _telemetry: telemetry, @@ -271,6 +273,11 @@ impl Service pub fn keystore(&self) -> &Keystore { &self.keystore } + + /// Get a handle to a future that will resolve on exit. + pub fn on_exit(&self) -> ::exit_future::Exit { + self.exit.clone() + } } impl Drop for Service where Components: components::Components {