diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a185bd50e0..00e753f7f2 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -534,6 +534,11 @@ name = "odds" version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "owning_ref" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "owning_ref" version = "0.3.3" @@ -542,6 +547,16 @@ dependencies = [ "stable_deref_trait 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.4.8" @@ -607,7 +622,8 @@ name = "polkadot-candidate-agreement" version = "0.1.0" dependencies = [ "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", - "polkadot-primitives 0.1.0", + "parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -955,6 +971,16 @@ dependencies = [ "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-id" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.4" @@ -1030,6 +1056,15 @@ dependencies = [ "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio-timer" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "triehash" version = "0.1.0" @@ -1178,7 +1213,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" "checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d" "checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" +"checksum owning_ref 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d52571ddcb42e9c900c901a18d8d67e393df723fcd51dd59c5b1a85d0acb6cc" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum parking_lot 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "fa12d706797d42551663426a45e2db2e0364bd1dbf6aeada87e89c5f981f43e9" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot_core 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f610cb9664da38e417ea3225f23051f589851999535290e077939838ab7a595" "checksum patricia-trie 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f1e2f638d79aba5c4a71a4f373df6e3cd702250a53b7f0ed4da1e2a7be9737ae" @@ -1217,6 +1254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +"checksum thread-id 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2af4d6289a69a35c4d3aea737add39685f2784122c28119a7713165a63d68c9d" "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" "checksum tiny-keccak 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52d12ad79e4063e0cb0ca5efa202ed7244b6ce4d25f4d3abe410b2a66128292" @@ -1224,6 +1262,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum tokio-io 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "514aae203178929dbf03318ad7c683126672d4d96eccb77b29603d33c9e25743" "checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" "checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum tokio-timer 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6131e780037787ff1b3f8aad9da83bca02438b72277850dd6ad0d455e0e20efc" "checksum triehash 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9291c7f0fae44858b5e087dd462afb382354120003778f1695b44aab98c7abd7" "checksum uint 0.1.0 (git+https://github.com/paritytech/primitives.git)" = "" "checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" diff --git a/substrate/candidate-agreement/Cargo.toml b/substrate/candidate-agreement/Cargo.toml index 9a2dc0ffb7..063a080926 100644 --- a/substrate/candidate-agreement/Cargo.toml +++ b/substrate/candidate-agreement/Cargo.toml @@ -5,4 +5,5 @@ authors = ["Parity Technologies "] [dependencies] futures = "0.1" -polkadot-primitives = { path = "../primitives" } +parking_lot = "0.3" +tokio-timer = "0.1.2" diff --git a/substrate/candidate-agreement/src/bft/mod.rs b/substrate/candidate-agreement/src/bft/mod.rs index b17092c451..71adbf609a 100644 --- a/substrate/candidate-agreement/src/bft/mod.rs +++ b/substrate/candidate-agreement/src/bft/mod.rs @@ -83,13 +83,13 @@ pub trait Context { /// A future that resolves when a round timeout is concluded. type RoundTimeout: Future; /// A future that resolves when a proposal is ready. - type Proposal: Future; + type CreateProposal: Future; /// Get the local validator ID. fn local_id(&self) -> Self::ValidatorId; /// Get the best proposal. - fn proposal(&self) -> Self::Proposal; + fn proposal(&self) -> Self::CreateProposal; /// Get the digest of a candidate. fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; @@ -247,7 +247,7 @@ enum LocalState { struct Strategy { nodes: usize, max_faulty: usize, - fetching_proposal: Option, + fetching_proposal: Option, round_timeout: future::Fuse, local_state: LocalState, locked: Option>, @@ -330,7 +330,7 @@ impl Strategy { -> Poll, E> where C::RoundTimeout: Future, - C::Proposal: Future, + C::CreateProposal: Future, { let mut last_watermark = ( self.current_accumulator.round_number(), @@ -363,7 +363,7 @@ impl Strategy { -> Poll, E> where C::RoundTimeout: Future, - C::Proposal: Future, + C::CreateProposal: Future, { self.propose(context, sending)?; self.prepare(context, sending); @@ -413,7 +413,7 @@ impl Strategy { } fn propose(&mut self, context: &C, sending: &mut Sending>) - -> Result<(), ::Error> + -> Result<(), ::Error> { if let LocalState::Start = self.local_state { let mut propose = false; @@ -629,7 +629,7 @@ impl Future for Agreement where C: Context, C::RoundTimeout: Future, - C::Proposal: Future, + C::CreateProposal: Future, I: Stream,Error=E>, O: Sink,SinkError=E>, E: From, diff --git a/substrate/candidate-agreement/src/bft/tests.rs b/substrate/candidate-agreement/src/bft/tests.rs index ff66ff0476..a7a3282cc9 100644 --- a/substrate/candidate-agreement/src/bft/tests.rs +++ b/substrate/candidate-agreement/src/bft/tests.rs @@ -104,13 +104,13 @@ impl Context for TestContext { type ValidatorId = ValidatorId; type Signature = Signature; type RoundTimeout = Box>; - type Proposal = FutureResult; + type CreateProposal = FutureResult; fn local_id(&self) -> ValidatorId { self.local_id.clone() } - fn proposal(&self) -> Self::Proposal { + fn proposal(&self) -> Self::CreateProposal { let proposal = { let mut p = self.proposal.lock().unwrap(); let x = *p; diff --git a/substrate/candidate-agreement/src/lib.rs b/substrate/candidate-agreement/src/lib.rs index 09dd56f5f0..6d106ab781 100644 --- a/substrate/candidate-agreement/src/lib.rs +++ b/substrate/candidate-agreement/src/lib.rs @@ -30,7 +30,168 @@ //! Groups themselves may be compromised by malicious validators. extern crate futures; -extern crate polkadot_primitives as primitives; +extern crate parking_lot; +extern crate tokio_timer; pub mod bft; pub mod table; + +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::hash::Hash; + +use futures::prelude::*; +use tokio_timer::Timer; + +use table::Table; + +/// Context necessary for agreement. +pub trait Context: Send + Clone { + /// A validator ID + type ValidatorId: Debug + Hash + Eq + Clone; + /// The digest (hash or other unique attribute) of a candidate. + type Digest: Debug + Hash + Eq + Clone; + /// The group ID type + type GroupId: Debug + Hash + Ord + Eq + Clone; + /// A signature type. + type Signature: Debug + Eq + Clone; + /// Candidate type. In practice this will be a candidate receipt. + type ParachainCandidate: Debug + Ord + Eq + Clone; + /// The actual block proposal type. This is what is agreed upon, and + /// is composed of multiple candidates. + type Proposal: Debug + Eq + Clone; + + /// A future that resolves when a candidate is checked for validity. + /// + /// In Polkadot, this will involve fetching the corresponding block data, + /// producing the necessary ingress, and running the parachain validity function. + type CheckCandidate: IntoFuture; + + /// A future that resolves when availability of a candidate's external + /// data is checked. + type CheckAvailability: IntoFuture; + + /// Get the digest of a candidate. + fn candidate_digest(candidate: &Self::ParachainCandidate) -> Self::Digest; + + /// Get the group of a candidate. + fn candidate_group(candidate: &Self::ParachainCandidate) -> Self::GroupId; + + /// Get the primary for a given round. + fn round_proposer(&self, round: usize) -> Self::ValidatorId; + + /// Check a candidate for validity. + fn check_validity(&self, candidate: &Self::ParachainCandidate) -> Self::CheckCandidate; + + /// Check availability of candidate data. + fn check_availability(&self, candidate: &Self::ParachainCandidate) -> Self::CheckAvailability; + + /// Attempt to combine a set of parachain candidates into a proposal. + /// + /// This may arbitrarily return `None`, but the intent is for `Some` + /// to only be returned when candidates from enough groups are known. + /// + /// "enough" may be subjective as well. + fn create_proposal(&self, candidates: Vec<&Self::ParachainCandidate>) + -> Option; + + /// Check validity of a proposal. This may also be somewhat subjective + /// based on a monotonic-decreasing curve. + fn proposal_valid(&self, proposal: &Self::Proposal) -> bool; + + /// Get the local validator ID. + fn local_id(&self) -> Self::ValidatorId; + + /// Sign a table validity statement with the local key. + fn sign_table_statement( + &self, + statement: &table::Statement + ) -> Self::Signature; + + /// Sign a BFT agreement message. + fn sign_bft_message(&self, &bft::Message) -> Self::Signature; +} + +/// Information about a specific group. +#[derive(Debug, Clone)] +pub struct GroupInfo { + /// Validators meant to check validity of candidates. + pub validity_guarantors: HashSet, + /// Validators meant to check availability of candidate data. + pub availability_guarantors: HashSet, + /// Number of votes needed for validity. + pub needed_validity: usize, + /// Number of votes needed for availability. + pub needed_availability: usize, +} + +struct TableContext { + context: C, + groups: HashMap>, +} + +impl table::Context for TableContext { + type ValidatorId = C::ValidatorId; + type Digest = C::Digest; + type GroupId = C::GroupId; + type Signature = C::Signature; + type Candidate = C::ParachainCandidate; + + fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest { + C::candidate_digest(candidate) + } + + fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId { + C::candidate_group(candidate) + } + + fn is_member_of(&self, validator: &Self::ValidatorId, group: &Self::GroupId) -> bool { + self.groups.get(group).map_or(false, |g| g.validity_guarantors.contains(validator)) + } + + fn is_availability_guarantor_of(&self, validator: &Self::ValidatorId, group: &Self::GroupId) -> bool { + self.groups.get(group).map_or(false, |g| g.availability_guarantors.contains(validator)) + } + + fn requisite_votes(&self, group: &Self::GroupId) -> (usize, usize) { + self.groups.get(group).map_or( + (usize::max_value(), usize::max_value()), + |g| (g.needed_validity, g.needed_availability), + ) + } +} + +/// Parameters necessary for agreement. +pub struct AgreementParams { + /// The context itself. + pub context: C, + /// For scheduling timeouts. + pub timer: Timer, + /// Group assignments. + pub groups: HashMap>, + /// The local candidate proposal. + // TODO: replace with future. + pub local_proposal: Option, +} + +pub fn agree(params: AgreementParams) { + let context = params.context; + let local_id = context.local_id(); + let mut table = Table::>::default(); + + let table_context = TableContext { + context: context.clone(), + groups: params.groups, + }; + + if let Some(candidate) = params.local_proposal { + let statement = table::Statement::Candidate(candidate); + let signed_statement = table::SignedStatement { + signature: context.sign_table_statement(&statement), + sender: local_id.clone(), + statement: statement, + }; + + table.import_statement(&table_context, signed_statement, None); + } +} diff --git a/substrate/candidate-agreement/src/table.rs b/substrate/candidate-agreement/src/table.rs index 381244e58b..864d189d6d 100644 --- a/substrate/candidate-agreement/src/table.rs +++ b/substrate/candidate-agreement/src/table.rs @@ -35,21 +35,21 @@ use std::fmt::Debug; /// Context for the statement table. pub trait Context { /// A validator ID - type ValidatorId: Hash + Eq + Clone + Debug; + type ValidatorId: Debug + Hash + Eq + Clone; /// The digest (hash or other unique attribute) of a candidate. - type Digest: Hash + Eq + Clone + Debug; - /// Candidate type. - type Candidate: Ord + Eq + Clone + Debug; + type Digest: Debug + Hash + Eq + Clone; /// The group ID type - type GroupId: Hash + Ord + Eq + Clone + Debug; + type GroupId: Debug + Hash + Ord + Eq + Clone; /// A signature type. - type Signature: Eq + Clone + Debug; + type Signature: Debug + Eq + Clone; + /// Candidate type. In practice this will be a candidate receipt. + type Candidate: Debug + Ord + Eq + Clone; /// get the digest of a candidate. - fn candidate_digest(&self, candidate: &Self::Candidate) -> Self::Digest; + fn candidate_digest(candidate: &Self::Candidate) -> Self::Digest; /// get the group of a candidate. - fn candidate_group(&self, candidate: &Self::Candidate) -> Self::GroupId; + fn candidate_group(candidate: &Self::Candidate) -> Self::GroupId; /// Whether a validator is a member of a group. /// Members are meant to submit candidates and vote on validity. @@ -259,13 +259,22 @@ pub fn create() -> Table { } /// Stores votes -#[derive(Default)] pub struct Table { validator_data: HashMap>, detected_misbehavior: HashMap::Misbehavior>, candidate_votes: HashMap>, } +impl Default for Table { + fn default() -> Self { + Table { + validator_data: HashMap::new(), + detected_misbehavior: HashMap::new(), + candidate_votes: HashMap::new(), + } + } +} + impl Table { /// Produce a set of proposed candidates. /// @@ -385,7 +394,7 @@ impl Table { candidate: C::Candidate, signature: C::Signature, ) -> (Option<::Misbehavior>, Option>) { - let group = context.candidate_group(&candidate); + let group = C::candidate_group(&candidate); if !context.is_member_of(&from, &group) { return ( Some(Misbehavior::UnauthorizedStatement(UnauthorizedStatement { @@ -400,7 +409,7 @@ impl Table { } // check that validator hasn't already specified another candidate. - let digest = context.candidate_digest(&candidate); + let digest = C::candidate_digest(&candidate); let new_proposal = match self.validator_data.entry(from.clone()) { Entry::Occupied(mut occ) => { @@ -608,11 +617,11 @@ mod tests { type GroupId = GroupId; type Signature = Signature; - fn candidate_digest(&self, candidate: &Candidate) -> Digest { + fn candidate_digest(candidate: &Candidate) -> Digest { Digest(candidate.1) } - fn candidate_group(&self, candidate: &Candidate) -> GroupId { + fn candidate_group(candidate: &Candidate) -> GroupId { GroupId(candidate.0) }