diff --git a/node/core/approvals/Cargo.toml b/node/core/approvals/Cargo.toml new file mode 100644 index 000000000000..236a37d20db1 --- /dev/null +++ b/node/core/approvals/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "polkadot-node-core-approvals" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +# futures = "0.3.5" +# futures-timer = "3.0.2" +# log = "0.4.8" +# derive_more = "0.99.9" +derive_more = "0.14.1" + +# bitvec = { version = "0.17.4", default-features = false, features = ["alloc"] } +# streamunordered = "0.5.1" + +sp-api = { 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" } +keystore = { package = "sc-keystore", git = "https://github.com/paritytech/substrate", branch = "master" } +primitives = { package = "sp-core", git = "https://github.com/paritytech/substrate", branch = "master" } + +polkadot-primitives = { path = "../../../primitives" } +polkadot-node-primitives = { path = "../../primitives" } +polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" } +# statement-table = { package = "polkadot-statement-table", path = "../../../statement-table" } + +babe-primitives = { package = "sp-consensus-babe", git = "https://github.com/paritytech/substrate", branch = "master" } +sc-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master"} +# sc-consensus-slots = { version = "0.8.0-rc3", git = "https://github.com/paritytech/substrate", branch = "master"} +merlin = { version = "2.0", default-features = false } +schnorrkel = { version = "0.9.1", features = ["u64_backend"], default-features = false } + +[dev-dependencies] +sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master" } +# futures = { version = "0.3.5", features = ["thread-pool"] } +# subsystem-test = { package = "polkadot-subsystem-test-helpers", path = "../../test-helpers/subsystem" } +# assert_matches = "1.3.0" + +[workspace] \ No newline at end of file diff --git a/node/core/approvals/src/assignments/announcer.rs b/node/core/approvals/src/assignments/announcer.rs new file mode 100644 index 000000000000..5e64e5898377 --- /dev/null +++ b/node/core/approvals/src/assignments/announcer.rs @@ -0,0 +1,261 @@ +//! Announcer for our own approval checking assignments +//! +//! + +use core::{ ops }; +use std::collections::{BTreeMap, HashSet, HashMap}; + +use schnorrkel::{Keypair}; + +use super::{ + ApprovalContext, AssigneeStatus, AssignmentResult, Hash, ParaId, + DelayTranche, + stories, + criteria::{self, Assignment, AssignmentSigned, Criteria, DelayCriteria, Position}, + tracker::{self, AssignmentsByDelay, Tracker}, + ValidatorId, +}; + + +impl Tracker { + /// Initialize tracking of both our own and others assignments and approvals + pub fn into_announcer(self, myself: Keypair) -> AssignmentResult { + let mut tracker = self; + let mut pending_relay_vrf_modulo = AssignmentsByDelay::default(); + let mut no_duplicates = HashSet::new(); + for sample in 0..tracker.context().num_samples() { + let a = Assignment::create( + criteria::RelayVRFModulo { sample }, + &tracker.relay_vrf_story, // tracker.access_story::() + &myself, + ).expect("RelayVRFModulo cannot error here"); + let context = tracker.context().clone(); + // We sample incorrect `ParaId`s here sometimes so just skip them. + if let Some(paraid) = a.paraid(&context) { + if ! no_duplicates.insert(paraid) { continue; } + pending_relay_vrf_modulo.insert_assignment_unchecked(a,&context); + } + } + let mut selfy = Announcer { + tracker, myself, + pending_relay_vrf_modulo, + pending_relay_vrf_delay: AssignmentsByDelay::default(), + pending_relay_equivocation: AssignmentsByDelay::default(), + announced_relay_vrf_modulo: AssignmentsSigned::default(), + announced_relay_vrf_delay: AssignmentsSigned::default(), + announced_relay_equivocation: AssignmentsSigned::default(), + }; + for paraid in selfy.tracker.context().paraids_by_core().clone().iter().filter_map(Option::as_ref) { + selfy.create_pending_delay(criteria::RelayVRFDelay { paraid: *paraid }) + .expect("Assignment::create cannot fail for RelayVRFDelay, only RelayEquivocation, qed"); + } + // TODO: We cannot announce here because we maybe require time to bump some work to larger delays + // selfy.advance_anv_slot(self.tracker.current_slot); + Ok(selfy) + } +} + + +pub struct AssignmentsSigned(BTreeMap >); + +impl Default for AssignmentsSigned { + fn default() -> Self { AssignmentsSigned(Default::default()) } +} + +// TODO: Access/output/serializtion methods, +// impl AssignmentsSigned { } + + +/// Track both our own and others assignments and approvals +pub struct Announcer { + /// Inheret the `Tracker` that built us + tracker: Tracker, + /// We require secret key access to invoke creation and signing of VRFs + /// + /// TODO: Actually substrate manages this another way, so change this part. + myself: Keypair, + /// Unannounced potential assignments with delay determined by relay chain VRF + /// TODO: We'll need this once we add functionality to delay work + pending_relay_vrf_modulo: AssignmentsByDelay, + /// Unannounced potential assignments with delay determined by relay chain VRF + pending_relay_vrf_delay: AssignmentsByDelay, + /// Unannounced potential assignments with delay determined by candidate equivocation + pending_relay_equivocation: AssignmentsByDelay, + /// Already announced assignments with determined by relay chain VRF + announced_relay_vrf_modulo: AssignmentsSigned, + /// Already announced assignments with delay determined by relay chain VRF + announced_relay_vrf_delay: AssignmentsSigned, + /// Already announced assignments with delay determined by candidate equivocation + announced_relay_equivocation: AssignmentsSigned, +} + +impl ops::Deref for Announcer { + type Target = Tracker; + fn deref(&self) -> &Tracker { &self.tracker } +} +impl ops::DerefMut for Announcer { + fn deref_mut(&mut self) -> &mut Tracker { &mut self.tracker } +} + +impl Announcer { + fn access_pending_mut(&mut self) -> &mut AssignmentsByDelay + where C: Criteria, Assignment: Position, + { + use core::any::Any; + (&mut self.pending_relay_vrf_modulo as &mut dyn Any) + .downcast_mut::>() + .or( (&mut self.pending_relay_vrf_delay as &mut dyn Any) + .downcast_mut::>() ) + .or( (&mut self.pending_relay_equivocation as &mut dyn Any) + .downcast_mut::>() ) + .expect("Oops, we've some foreign type or RelayVRFDelay as DelayCriteria!") + } + + fn create_pending_delay(&mut self, criteria: C) -> AssignmentResult<()> + where C: DelayCriteria, Assignment: Position, + { + let context = self.tracker.context().clone(); + // We skip absent `ParaId`s when creating any pending assignemnts without error, but.. + if context.core_by_paraid( criteria.paraid() ).is_none() { return Ok(()); } + let a = Assignment::create(criteria, self.tracker.access_story::(), &self.myself) ?; + self.access_pending_mut::().insert_assignment_unchecked(a, &context); + Ok(()) + } + + fn id(&self) -> ValidatorId { + criteria::validator_id_from_key(&self.myself.public) + } + + /// Access outgoing announcement set immutably + pub(super) fn access_announced(&mut self) -> &AssignmentsSigned + where C: DelayCriteria, Assignment: Position, + { + use core::any::Any; + (&self.announced_relay_vrf_modulo as &dyn Any).downcast_ref::>() + .or( (&self.announced_relay_vrf_delay as &dyn Any).downcast_ref::>() ) + .or( (&self.announced_relay_equivocation as &dyn Any).downcast_ref::>() ) + .expect("Oops, we've some foreign type as Criteria!") + } + + /// Access outgoing announcements set mutably + pub(super) fn access_announced_mut(&mut self) -> &mut AssignmentsSigned + where C: Criteria, + { + use core::any::Any; + (&mut self.announced_relay_vrf_modulo as &mut dyn Any) + .downcast_mut::>() + .or( (&mut self.announced_relay_vrf_delay as &mut dyn Any) + .downcast_mut::>() ) + .or( (&mut self.announced_relay_equivocation as &mut dyn Any) + .downcast_mut::>() ) + .expect("Oops, we've some foreign type as Criteria!") + } + + /// Announce any unannounced assignments from the given tranche + /// as filtered by the provided closure. + /// + /// TODO: It'll be more efficent to operate on ranges here + fn announce_pending_with<'a,C,F>(&'a mut self, tranche: DelayTranche, f: F) + where C: Criteria, Assignment: Position, Assignment: Position, + F: 'a + FnMut(&Assignment) -> bool, + { + let mut vs: Vec> = self.access_pending_mut::() + .drain_filter(tranche..tranche+1,f).collect(); + for a in vs { + let context = self.tracker.context().clone(); + let recieved = self.tracker.current_delay_tranche(); + let paraid = a.paraid(&context) + .expect("Announcing assignment for `ParaId` not assigned to any core."); + let a = a.sign(&context, &self.myself, recieved); + let a_signed = a.to_signed(context); + // Importantly `insert_assignment` computes delay tranche + // from the assignment which determines priority. We may + // have extra delay in `a.vrf_signature.recieved` which + // only determines when it becomes a no show. + self.tracker.insert_assignment(a,true) + .expect("First, we insert only for paraids assigned to cores here because this assignment gets fixed by the relay chain block. Second, we restrict each criteria to doing only one assignment per paraid, so we cannot find any duplicates. Also, we've already removed the pending assignment above, making `candidate.checkers` empty."); + self.access_announced_mut::().0.insert(paraid,a_signed); + } + } + + /// Announce any unannounced assignments from the given tranche + /// as filtered by the provided closure. + /// + fn announce_pending_from_assignees( + &mut self, + tranche: DelayTranche, + context: &ApprovalContext, + assignees: &mut HashMap + ) + where C: Criteria, Assignment: Position, + { + self.announce_pending_with::(tranche, + |a| if let Some(paraid) = a.paraid(context) { + let b = assignees.get(¶id) + // We admit a.delay_tranche() < tranche here because + // `self.pending_*` could represent posponed work. + .filter( |c| a.delay_tranche(context) <= c.tranche().unwrap() ) + .is_some(); + if b { assignees.remove(¶id); } + b + } else { false } + ) + } + + /// Advances the AnV slot aka time to the specified value, + /// enquing any pending announcements too. + pub fn advance_anv_slot(&mut self, new_slot: u64) { + // We allow rerunning this with the current slot right now, but.. + if new_slot < self.tracker.current_slot { return; } + + let new_delay_tranche = self.delay_tranche(new_slot) + .expect("new_slot > current_slot > context.anv_slot_number"); + let now = self.current_delay_tranche(); + // let myself = self.id(); + + // We first reconstruct the current assignee status for any unapproved + // sessions, including all current announcements. + let mut relay_vrf_assignees = HashMap::new(); + let mut relay_equivocation_assignees = HashMap::new(); + for (paraid,candidate) in self.tracker.candidates() { + // We cannot skip previously approved checks here because + // we could announce ourself as RelayEquivocation checkers + // even after fulfilling a RelayVRF assignment. Yet, we'd + // love something like this, maybe two announced flags. + // if candidate.is_approved_by_checker(&myself) { continue; } + + let c = candidate.assignee_status::(now); + if ! c.is_approved() { relay_vrf_assignees.insert(*paraid,c); } + let c = candidate.assignee_status::(now); + if ! c.is_approved() { relay_equivocation_assignees.insert(*paraid,c); } + } + + let context = self.tracker.context().clone(); + for tranche in 0..now { + self.announce_pending_from_assignees:: + (tranche, &context, &mut relay_vrf_assignees); + self.announce_pending_from_assignees:: + (tranche, &context, &mut relay_vrf_assignees); + self.announce_pending_from_assignees:: + (tranche, &context, &mut relay_equivocation_assignees); + // We avoid recomputing assignee statuses inside this loop + // becuase we never check any given candidate more than once + } + } + + /// Mark myself as approving this candiddate + pub fn approve_mine(&mut self, paraid: &ParaId) -> AssignmentResult<()> { + let myself = self.id(); + self.tracker.candidate_mut(paraid)?.approve(myself, true) ?; + // TODO: We could restrict this to the current paraid of course. + self.advance_anv_slot(self.tracker.current_slot); + Ok(()) + } + + // Major TODO: Add RelayEquivocations that calls + // self.create_pending_delay(criteria::RelayEquivocation { paraid: *paraid }) + + // Major TODO: Add announcement posponment system + // Big question: What inputs? How much foresight? Where smart? +} + diff --git a/node/core/approvals/src/assignments/criteria.rs b/node/core/approvals/src/assignments/criteria.rs new file mode 100644 index 000000000000..e18a8baf83bf --- /dev/null +++ b/node/core/approvals/src/assignments/criteria.rs @@ -0,0 +1,392 @@ +//! Approval checker asignment VRF criteria +//! +//! We manage the actual VRF computations for approval checker +//! assignments inside this module, so most schnorrkell logic gets +//! isolated here. +//! +//! TODO: We should expand RelayVRFModulo to do rejection sampling +//! using `vrf::vrf_merge`, which requires `Vec<..>`s for +//! `AssignmentSigned::vrf_preout` and `Assignment::vrf_inout`. + +use core::{borrow::Borrow, convert::TryFrom}; + +use merlin::Transcript; +use schnorrkel::{PublicKey, PUBLIC_KEY_LENGTH, Keypair, vrf}; + +// pub use sp_consensus_vrf::schnorrkel::{Randomness, VRF_PROOF_LENGTH, VRF_OUTPUT_LENGTH, RANDOMNESS_LENGTH }; + + +use crate::Error; + +use super::{ + ApprovalContext, AssignmentResult, Hash, ParaId, DelayTranche, + stories, // RelayVRFStory, RelayEquivocationStory + ValidatorId, +}; + + +pub(super) fn validator_id_from_key(key: &PublicKey) -> ValidatorId { + unimplemented!() // checker.public.to_bytes() except we need substrate's stuff here +} + + +impl ApprovalContext { + pub fn transcript(&self) -> Transcript { + let mut t = Transcript::new(b"Approval Assignment Signature"); + t.append_u64(b"rad slot", self.slot); + t.append_u64(b"rad epoch", self.epoch); + t.append_message(b"rad block", self.hash.as_ref()); + use primitives::crypto::Public; + t.append_message(b"rad authority", &self.authority.to_raw_vec()); // Vec WTF?!? + t + } +} + + +/// Approval checker assignment criteria +/// +/// We determine how the relay chain contet, any criteria data, and +/// any relevant stories impact VRF invokation using this trait, +pub trait Criteria : Clone + 'static { + /// Additionl data required for constructing the VRF input + type Story; + + /// Write the transcript from which build the VRF input. + /// + /// Cannot error unless `Criteria = RelayEquivocation` + fn vrf_input(&self, story: &Self::Story) -> AssignmentResult; + + /// Initialize the transcript for our Schnorr DLEQ proof. + /// + /// Any criteria data that requires authentication, which should make + /// signing gossip messages unecessary, saving 64 bytes, etc. + fn extra(&self, context: &ApprovalContext) -> Transcript { + context.transcript() + } + + // fn position(&self, vrf_inout: &vrf::VRFInOut) -> Position; +} + + +/// Initial approval checker assignment based upon checkers' VRF +/// applied to the relay chain VRF, but then computed modulo the +/// number of parachains. +#[derive(Clone)] +pub struct RelayVRFModulo { + pub(crate) sample: u16, + // Story::anv_rc_vrf_source +} + +impl Criteria for RelayVRFModulo { + type Story = stories::RelayVRFStory; + + /// Never errors. + fn vrf_input(&self, story: &Self::Story) -> AssignmentResult { + if self.sample > 0 { return Err(Error::BadAssignment("RelayVRFModulo does not yet support additional samples")); } + let mut t = Transcript::new(b"Approval Assignment VRF"); + t.append_message(b"RelayVRF", &story.anv_rc_vrf_source ); + t.append_u64(b"Sample", self.sample.into() ); + Ok(t) + } +} + +// impl RelayVRFInitial { } + + +/// Approval checker assignment based upon checkers' VRF applied +/// to the relay chain VRF and parachain id, but then outputing a +/// delay. Applies only if too few check before reaching the delay. +#[derive(Clone)] +pub struct RelayVRFDelay { + // Story::anv_rc_vrf_source + pub(crate) paraid: ParaId, +} + +impl Criteria for RelayVRFDelay { + type Story = stories::RelayVRFStory; + + /// Never errors + fn vrf_input(&self, story: &Self::Story) -> AssignmentResult { + let mut t = Transcript::new(b"Approval Assignment VRF"); + t.append_message(b"RelayVRFDelay", &story.anv_rc_vrf_source ); + t.append_u64(b"ParaId", u32::from(self.paraid).into() ); + Ok(t) + } +} + +// impl RelayVRFDelay { } + + +/// Approval checker assignment based upon parablock hash +/// of a candidate equivocation. +#[derive(Clone)] +pub struct RelayEquivocation { + // Story::anv_rc_vrf_source + pub(crate) paraid: ParaId, +} + +impl Criteria for RelayEquivocation { + type Story = stories::RelayEquivocationStory; + + /// Write the transcript from which build the VRF input for + /// additional approval checks triggered by relay chain equivocations. + /// + /// Errors if paraid does not yet count as a candidate equivocation + fn vrf_input(&self, story: &Self::Story) -> AssignmentResult { + let h = story.candidate_equivocations.get(&self.paraid) + .ok_or(Error::BadStory("Not a candidate equivocation")) ?; + let mut t = Transcript::new(b"Approval Assignment VRF"); + t.append_u64(b"ParaId", u32::from(self.paraid).into() ); + t.append_message(b"Candidate Equivocation", h.as_ref() ); + Ok(t) + } +} + + +/// Internal representation for a assigment with some computable +/// delay. +/// We should obtain these first by verifying a signed +/// assignment using `AssignmentSigned::verify`, or simularly using +/// `Criteria::attach` manually, and secondly by evaluating our own +/// criteria. In the later case, we produce a signed assignment +/// by calling `Assignment::sign`. +pub struct Assignment { + /// Assignment criteria specific data + criteria: C, + /// Assignment's VRF signature including its checker's key + vrf_signature: K, + /// VRFInOut from which we compute the actualy assignment details + /// We could save some space by storing a `VRFPreOut` in + /// `VRFSignature`, and storing some random output here. + vrf_inout: vrf::VRFInOut, +} + +impl Assignment where C: Criteria { + /// Identify the checker + pub fn checker(&self) -> &ValidatorId { &self.vrf_signature.checker } + + pub(super) fn checker_n_recieved(&self) -> (ValidatorId,DelayTranche) + { (self.vrf_signature.checker.clone(), self.vrf_signature.recieved) } + + /// Return our `AssignmentSigned` + pub fn to_signed(&self, context: ApprovalContext) -> AssignmentSigned { + AssignmentSigned { + context, + criteria: self.criteria.clone(), + checker: self.vrf_signature.checker.clone(), + vrf_preout: self.vrf_inout.to_output().to_bytes(), + vrf_proof: self.vrf_signature.vrf_proof.clone() + } + } +} + +impl Assignment where C: Criteria { + /// Create our own `Assignment` for the given criteria, story, + /// and our keypair, by constructing its `VRFInOut`. + pub fn create(criteria: C, story: &C::Story, checker: &Keypair) -> AssignmentResult> { + let vrf_inout = checker.borrow().vrf_create_hash(criteria.vrf_input(story) ?); + Ok(Assignment { criteria, vrf_signature: (), vrf_inout, }) + } + + /// VRF sign our assignment for announcment. + /// + /// We could take `K: Borrow` above in `create`, saving us + /// the `checker` argument here, and making `K=Arc` work, + /// except `Assignment`s always occur with so much repetition that + /// passing the `Keypair` again makes more sense. + pub fn sign( + &self, + context: &ApprovalContext, + checker: &Keypair, + recieved: DelayTranche, + ) -> Assignment { + // Must exactly mirror `schnorrkel::Keypair::vrf_sign_extra` + // or else rerun one point multiplicaiton in vrf_create_hash + let t = self.criteria.extra(context); + let vrf_proof = checker.dleq_proove(t, &self.vrf_inout, vrf::KUSAMA_VRF).0.to_bytes(); + let checker = validator_id_from_key(&checker.public); + let vrf_signature = AssignmentSignature { checker, vrf_proof, recieved }; + Assignment { criteria: self.criteria.clone(), vrf_signature, vrf_inout: self.vrf_inout.clone(), } + } +} + + +/// Assignment's VRF signature. +#[derive(Clone)] +pub struct AssignmentSignature { + /// Signer's public key + checker: ValidatorId, + /// DLEQ Proof of the VRF mapping the story to pre-output, + /// and singing the context as well. + vrf_proof: [u8; vrf::VRF_PROOF_LENGTH], + /// Actual delay tranche when we recieved this announcement. + /// + /// We never transfer this inside the `SignedAssignment`, but + /// compute it outrelves when verifying assignment notices. + /// It prevents us counting late announcements as no shows too + /// early, which permits delayed annoucements, such as caused + /// by others' being no shows or to improve workload balancing. + recieved: DelayTranche, +} + +/// Announcable VRF signed assignment +pub struct AssignmentSigned { + context: ApprovalContext, + criteria: C, + /// Signer's public key + checker: ValidatorId, + /// VRF Pre-Output computed from the story associated to this + /// context and criteria, and proven correct by vrf_proof, but + /// only yields output with `make_bytes` calls on its `VRFInOut`. + vrf_preout: [u8; vrf::VRF_OUTPUT_LENGTH], + /// DLEQ Proof of the VRF mapping the story to pre-output, + /// and singing the context as well. + vrf_proof: [u8; vrf::VRF_PROOF_LENGTH], +} + +impl AssignmentSigned { + /// Get checker identity + pub fn checker(&self) -> &ValidatorId { &self.checker } + + /// Get publickey identifying checker + fn checker_pk(&self) -> AssignmentResult { + use primitives::crypto::Public; + PublicKey::from_bytes(&self.checker.to_raw_vec()) // Vec WTF?!? + .map_err(|_| Error::BadAssignment("Bad VRF signature (bad publickey)")) + } + + /// Verify a signed assignment + pub fn verify(&self, story: &C::Story, recieved: DelayTranche) + -> AssignmentResult<(&ApprovalContext,Assignment)> + { + let AssignmentSigned { context, criteria, vrf_preout, checker, vrf_proof, } = self; + let vrf_signature = AssignmentSignature { + checker: checker.clone(), + vrf_proof: vrf_proof.clone(), + recieved, + }; + let checker_pk = self.checker_pk() ?; + let vrf_inout = vrf::VRFOutput::from_bytes(vrf_preout) + .expect("length enforced statically") + .attach_input_hash(&checker_pk, criteria.vrf_input(story) ?) + .map_err(|_| Error::BadAssignment("Bad VRF signature (bad pre-output)")) ?; + let vrf_proof = vrf::VRFProof::from_bytes(vrf_proof) + .map_err(|_| Error::BadAssignment("Bad VRF signature (bad proof)")) ?; + let t = criteria.extra(&context); + let _ = checker_pk.dleq_verify(t, &vrf_inout, &vrf_proof, vrf::KUSAMA_VRF) + .map_err(|_| Error::BadAssignment("Bad VRF signature (invalid)")) ?; + Ok((context, Assignment { criteria: criteria.clone(), vrf_signature, vrf_inout, })) + } + + pub(super) fn serialize(&self) -> Vec { + unimplemented!() + } +} + + +pub(crate) enum SomeCriteria { + RelayVRFModulo(RelayVRFModulo), + RelayVRFDelay(RelayVRFDelay), + RelayEquivocation(RelayEquivocation), +} + +impl AssignmentSigned { + pub(super) fn deserialize(buf: &[u8]) -> AssignmentSigned { + unimplemented!() + } +} + + +/// We require `Assignment` methods generic over `C` +/// that position this assignment inside the assignment tracker +/// +/// We pass `ApprovalContext` into both methods for availability core +/// information. We need each cores' paraid assignment for `paraid` +/// of course, but `delay_tranche` only requires the approximate +/// number of availability cores, so we might avoid passing it there +/// in future once that number solidifies. +pub(super) trait Position { + /// Assignment's our `ParaId` from allowed `ParaId` returnned by + /// `stories::allowed_paraids`. + fn paraid(&self, context: &ApprovalContext) -> Option; + + /// Always assign `RelayVRFModulo` the zeroth delay tranche + fn delay_tranche(&self, context: &ApprovalContext) -> DelayTranche { 0 } +} + +impl Position for Assignment { + /// Assign our `ParaId` from allowed `ParaId` returnned by + /// `stories::allowed_paraids`. + fn paraid(&self, context: &ApprovalContext) -> Option { + // TODO: Optimize accessing this from `ApprovalContext` + let paraids = context.paraids_by_core(); + // We use u64 here to give a reasonable distribution modulo the number of parachains + let mut core = u64::from_le_bytes(self.vrf_inout.make_bytes::<[u8; 8]>(b"core")); + core %= paraids.len() as u64; // assumes usize < u64 + paraids[core as usize] + } + + /// Always assign `RelayVRFModulo` the zeroth delay tranche + fn delay_tranche(&self, _context: &ApprovalContext) -> DelayTranche { 0 } +} + + +/// Approval checker assignment criteria that fully utilizes delays. +/// +/// We require this helper trait to help unify the handling of +/// `RelayVRFDelay` and `RelayEquivocation`. +pub trait DelayCriteria : Criteria { + /// All delay based assignment criteria contain an explicit paraid + fn paraid(&self) -> ParaId; + /// We consolodate this many plus one delays at tranche zero, + /// ensuring they always run their checks. + fn zeroth_delay_tranche_width() -> DelayTranche; +} +impl DelayCriteria for RelayVRFDelay { + fn paraid(&self) -> ParaId { self.paraid } + /// We do not techncially require delay tranche zero checkers here + /// thanks to `RelayVRFModulo`, but they help us tune the expected + /// checkers, and our simple impl for `Position::delay_tranche` + /// imposes at least one tranche worth. + /// + /// If security dictates more zeroth delay checkers then we prefer + /// adding allocations by `RelayVRFModulo` instead. + fn zeroth_delay_tranche_width() -> DelayTranche { 0 } // 1 +} +impl DelayCriteria for RelayEquivocation { + fn paraid(&self) -> ParaId { self.paraid } + /// Assigns twelve tranches worth of checks into delay tranch zero, + /// meaning they always check candidate equivocations. + /// + /// We do need some consolodation at zero for `RelayEquivocation`. + /// We considered some modulo condition using relay chain block hashes, + /// except we're already slashing someone for equivocation, so being + /// less efficent hurts less than the extra code complexity. + fn zeroth_delay_tranche_width() -> DelayTranche { 11 } // 12 +} + +impl Position for Assignment where C: DelayCriteria { + /// Assign our `ParaId` from the one explicitly stored, but error + /// if disallowed by `stories::allowed_paraids`. + /// + /// Errors if the paraid is not declared available here. + fn paraid(&self, context: &ApprovalContext) -> Option { + use core::ops::Deref; + let paraid = self.criteria.paraid(); + // TODO: Speed up! Cores are not sorted so no binary_search here + if context.core_by_paraid(paraid).is_none() { return None; } + Some(paraid) + } + + /// Assign our delay using our VRF output + fn delay_tranche(&self, context: &ApprovalContext) -> DelayTranche { + let delay_tranche_modulus = context.num_delay_tranches() + .saturating_add( C::zeroth_delay_tranche_width() ); + // We use u64 here to give a reasonable distribution modulo the number of tranches + let mut delay_tranche = u64::from_le_bytes(self.vrf_inout.make_bytes::<[u8; 8]>(b"tranche")); + delay_tranche %= delay_tranche_modulus as u64; + delay_tranche.saturating_sub(C::zeroth_delay_tranche_width() as u64) as u32 + } +} + + diff --git a/node/core/approvals/src/assignments/mod.rs b/node/core/approvals/src/assignments/mod.rs new file mode 100644 index 000000000000..9598afa4efc0 --- /dev/null +++ b/node/core/approvals/src/assignments/mod.rs @@ -0,0 +1,108 @@ +//! Approval checker assignments module +//! +//! Approval validity checks determine whether Polkadot considers a parachain candidate valid. +//! We distinguish them from backing validity checks that merely determine whether Polakdot +//! should begin processing a parachain candidate. + + +use std::collections::BTreeMap; + +use polkadot_primitives::v1::{Id as ParaId, ValidatorId, Hash, Header}; + + +use crate::Error; +pub type AssignmentResult = Result; + +pub mod stories; +pub mod criteria; +pub mod tracker; +pub mod announcer; + + +pub use stories::ApprovalContext; + +pub type DelayTranche = u32; + + + +/// Approvals target levels +/// +/// We instantiuate this with `Default` currently, but we'll want the +/// relay VRF target number to be configurable by the chain eventually. +pub struct ApprovalTargets { + /// Approvals required for criteria based upon relay chain VRF output, + /// never too larger, never too small. + pub relay_vrf_checkers: u32, + /// Approvals required for criteria based upon relay chain equivocations, + /// initially zero but increased if we discover equivocations. + pub relay_equivocation_checkers: u32, + /// How long we wait for no shows + pub noshow_timeout: u32, +} + +impl Default for ApprovalTargets { + fn default() -> Self { + ApprovalTargets { + relay_vrf_checkers: 20, // We've no analysis backing this choice yet. + relay_equivocation_checkers: 0, + noshow_timeout: 24, // Two relay chain blocks + } + } +} + +impl ApprovalTargets { + /// Target number of checkers by story type + fn target(&self) -> u32 { + use core::any::TypeId; + let s = TypeId::of::(); + if s == TypeId::of::() + { self.relay_vrf_checkers } + else if s == TypeId::of::() + { self.relay_equivocation_checkers } + else { panic!("Oops, we've some foreign type for Criteria::Story!") } + } +} + + +/// +#[derive(Clone)] +pub struct AssigneeStatus { + /// Highest tranche considered plus one + tranche: DelayTranche, + /// Required minimum tranche + pub min_tranche: DelayTranche, + /// Assignement target, including increases due to no shows. + pub target: u32, + /// Assigned validators. + pub assigned: u32, + /// Awating approvals, including no shows. + pub waiting: u32, + /// Total no shows, including our debt. + pub noshows: u32, + /// Any no shows not yet addressed by additional tranches, + /// often zero since adding extra tranches pays the debt fast. + pub debt: u32, + /// Approval votes thus far. + pub approved: u32, + /// How long we wait for no shows + /// + /// Increases if we're replacing no shows from multiple tranches + pub noshow_timeout: u32, +} + +impl AssigneeStatus { + /// Highest tranche considered thus far + pub fn tranche(&self) -> Option { + self.tranche.checked_sub(1) + } + + pub fn is_approved(&self) -> bool { + debug_assert!(self.assigned == self.assigned + self.waiting); + self.target == self.approved + && self.approved == self.assigned + && self.waiting == 0 + && self.tranche >= self.min_tranche + } +} + + diff --git a/node/core/approvals/src/assignments/stories.rs b/node/core/approvals/src/assignments/stories.rs new file mode 100644 index 000000000000..5ea836cc1a8a --- /dev/null +++ b/node/core/approvals/src/assignments/stories.rs @@ -0,0 +1,229 @@ +//! Chain stories supporting approval assignment criteria +//! +//! We compute approval checker assignment criteria with VRF outputs, +//! but their correxponding VRF inputs come from information that +//! ideally lives on-chain. In this submodule, we either retrieve +//! such information provided it exists on-chain, or else revalidate +//! it when it lives off-chain, and then create the "(chain) stories" +//! actually use in validating assignment criteria. +//! In short, stories isolate our data dependencies upon the relay chain. + +use std::sync::Arc; +use std::collections::HashMap; + +use babe_primitives::{EquivocationProof, AuthorityId, make_transcript}; +use sc_consensus_babe::{Epoch}; +// use sc_consensus_slots::{EquivocationProof}; +// use sp_consensus_babe::{EquivocationProof}; +// https://github.com/paritytech/substrate/pull/6362/files#diff-aee164b6a1b80d52767f208971d01d82R32 + +use super::{AssignmentResult, DelayTranche, ParaId, Hash, Header, Error, ValidatorId}; + + +/// A slot number. +pub type SlotNumber = u64; + +/// A epoch number. +pub type EpochNumber = u64; + + +pub const ANV_SLOTS_PER_BP_SLOTS: u64 = 12; // = 6*2, so every half second + +pub const NOSHOW_DELAY_TRANCHES: super::DelayTranche = 24; // Twice ANV_SLOTS_PER_BP_SLOTS ?? + + +/// Identifies the relay chain block in which we declared these +/// parachain candidates to be availability +#[derive(Clone,PartialEq,Eq)] +pub struct ApprovalContext { + /// Relay chain slot number of availability declaration in the relay chain + pub(crate) slot: SlotNumber, + /// Epoch in which slot occurs + pub(crate) epoch: EpochNumber, + /// Block hash + pub(crate) hash: Hash, + /// Block producer + pub(crate) authority: ValidatorId, +} + +impl ApprovalContext { + pub fn anv_slot_number(&self) -> SlotNumber { + self.slot.checked_mul(ANV_SLOTS_PER_BP_SLOTS) + .expect("Almost 2^60 seconds elapsed?!?") + } + + pub fn new(checker: ValidatorId) -> AssignmentResult { + let slot: u64 = unimplemented!(); + let epoch: u64 = unimplemented!(); + let hash: Hash = unimplemented!(); + let authority: ValidatorId = unimplemented!(); + Ok(ApprovalContext { slot, epoch, hash, authority }) + } + + /// Relay chain block production slot + pub fn slot(&self) -> u64 { self.slot } + + /// Relay chain block production epoch + pub fn epoch(&self) -> u64 { self.epoch } + + /// Relay chain block hash + pub fn hash(&self) -> &Hash { &self.hash } + + /// Fetch full epoch data from self.epoch + pub fn fetch_epoch(&self) -> Epoch { + unimplemented!() + } + + /// Fetch full epoch data from self.epoch + pub fn fetch_header(&self) -> Header { + unimplemented!() + } + + /// Assignments of `ParaId` to ailability cores for the current + /// `epoch` and `slot`. + /// + /// We suggest any full parachains have their cores allocated by + /// the epoch randomness from BABE, so parachain cores should be + /// allocated using a permutation, maybe Fisher-Yates shuffle, + /// seeded by the hash of the babe_epoch_randomness and the + /// slot divided by some small constant. + /// + /// We do not minimize aversarial manipulation for parathreads + /// similarly however because we operate under the assumption + /// that an adversary with enough checkers for an attack should + /// possess block production capability for most parachains. + /// We still favor scheduling parathreads onto availability cores + /// earlier rather than later however. + pub(super) fn paraids_by_core(&self) -> &Arc<[Option]> { + unimplemented!() + } + + pub(super) fn paraids(&self) -> impl Iterator + '_ { + self.paraids_by_core().iter().filter_map(Option::as_ref).cloned() + } + + /// Assignments of `ParaId` to ailability cores for the current + /// `epoch` and `slot`. + /// + /// TODO: Return `CoreId`. Improve performance!!! + pub(super) fn core_by_paraid(&self, paraid: ParaId) -> Option<()> { + if ! self.paraids_by_core().contains(&Some(paraid)) { return None; } + Some(()) + } + + /// Availability core supply + /// + /// An upper bound on `self.paraids_by_core().len()` that remains + /// constant within an epoch. Any changes should obey code change + /// rules and thus be delayed one epoch. + pub fn max_cores(&self) -> u32 { 128 } + + /// We set two delay tranches per core so that each tranche expects + /// half as many checkers as the number of backing checkers. + pub fn delay_tranches_per_core(&self) -> DelayTranche { 2 } + + /// Maximum number of delay tranches + /// + /// We do not set this differently for RelayEquivocationStory and + /// RelayVRF because doing so complicates the code needlessly, and + /// this bound should never be reached by either. + pub fn num_delay_tranches(&self) -> DelayTranche { + self.max_cores().saturating_mul( self.delay_tranches_per_core() ) + } + + /// We sample in `RingVRFModulo` this many VRF inputs from the + /// relay chain VRF to populate our zeroth delay tranche. + pub fn num_samples(&self) -> u16 { 3 } + + /// Create story for assignment criteria determined by relay chain VRFs. + /// + /// We must only revalidate the relay chain VRF, by supplying the proof, + /// if we have not already done so when importing the block. + pub fn new_vrf_story(&self) + -> AssignmentResult + { + let header = self.fetch_header(); + let vrf_t = make_transcript( + &self.fetch_epoch().randomness, + self.slot, + self.epoch // == self.epoch().epoch_index, + ); + // TODO: Should we check this block's proof again? I suppose no, but.. + let vrf_proof: Option<&::schnorrkel::vrf::VRFProof> = None; + + let authority_id: AuthorityId = unimplemented!(); + use primitives::crypto::Public; + let publickey = ::schnorrkel::PublicKey::from_bytes(&authority_id.to_raw_vec()) // Vec WTF?!? + .map_err(|_| Error::BadStory("Relay chain block authorized by improper sr25519 public key")) ?; + + let vrf_preout = unimplemented!(); + let vrf_io = if let Some(pr) = vrf_proof { + publickey.vrf_verify(vrf_t,vrf_preout,pr) + .map_err(|_| Error::BadStory("Relay chain block VRF failed validation")) ?.0 + } else { + unimplemented!(); // TODO: Verify that we imported this block? + vrf_preout.attach_input_hash(&publickey,vrf_t) + .map_err(|_| Error::BadStory("Relay chain block with invalid VRF PreOutput")) ? + }; + + let anv_rc_vrf_source = vrf_io.make_bytes::<[u8; 32]>(b"A&V RC-VRF"); + // above should be based on https://github.com/paritytech/substrate/pull/5788/files + + Ok(RelayVRFStory { anv_rc_vrf_source, }) + } + + /// Initalize empty story for a relay chain block with no equivocations so far. + /// + /// We must only revalidate the relay chain VRF, by supplying the proof, + /// if we have not already done so when importing the block. + pub fn new_equivocation_story(&self) -> RelayEquivocationStory { + let header = self.fetch_header(); + RelayEquivocationStory { header, relay_equivocations: Vec::new(), candidate_equivocations: HashMap::new() } + } +} + + +/// Approval assignment story comprising a relay chain VRF output +pub struct RelayVRFStory { + /// Actual final VRF output computed from the relay chain VRF + pub(super) anv_rc_vrf_source: [u8; 32], +} + +/// Approval assignments whose availability declaration is an equivocation +pub struct RelayEquivocationStory { + /// Relay chain block hash + pub(super) header: Header, + /// Relay chain equivocations from which we compute candidate equicocations + pub(super) relay_equivocations: Vec
, + /// Actual candidate equicocations and rtheir block hashes + pub(super) candidate_equivocations: HashMap, +} + +impl RelayEquivocationStory { + /// Add any candidate equivocations found within a relay chain equivocation. + /// + /// We define a candidate equivocation in a relay chain block X as + /// a candidate declared available in X but not declared available + /// in some relay chain block production equivocation Y of X. + /// + /// We know all `EquivocationProof`s were created by calls to + /// `sp_consensus_slots::check_equivocation`, so they represent + /// real relay chainlock production bequivocations, and need + /// not be rechecked here. + pub fn add_equivocation(&mut self, ep: &EquivocationProof
) + -> AssignmentResult<()> + { + let slot = ep.slot_number; + let headers = [&ep.first_header, &ep.second_header]; + let (i,our_header) = headers.iter() + .enumerate() + .find( |(_,h)| h.hash() == self.header.hash() ) + .ok_or(Error::BadStory("Cannot add unrelated equivocation proof.")) ?; + let other_header = headers[1-i]; + self.relay_equivocations.push(other_header.clone()); + + unimplemented!() + // TODO: Iterate over candidates in our_header and add to self.candidate_equivocations any that differ or do not exist in other_header. We should restrict to tracker.candidates somehow if initalize_candidate could be called on fewer than all candidates, but we'll leave this code here until we descide if we want that functionality. + } +} + diff --git a/node/core/approvals/src/assignments/tracker.rs b/node/core/approvals/src/assignments/tracker.rs new file mode 100644 index 000000000000..4ba1013943ac --- /dev/null +++ b/node/core/approvals/src/assignments/tracker.rs @@ -0,0 +1,534 @@ +//! Approval assignment tracker +//! +//! We mostly plumb information from stories into criteria method +//! invokations in this module, which +//! + +use core::{ cmp::{max,min}, convert::TryFrom, ops, }; +use std::collections::{BTreeMap, HashMap, hash_map::Entry}; + +use crate::Error; + +use super::{ + ApprovalContext, ApprovalTargets, AssigneeStatus, AssignmentResult, + Hash, ParaId, DelayTranche, + stories, + criteria::{self, Assignment, AssignmentSigned, Criteria, Position}, + ValidatorId, +}; + + +/// Verified assignments sorted by their delay tranche +/// +// #[derive(..)] +pub(super) struct AssignmentsByDelay + (BTreeMap >>); + +impl Default for AssignmentsByDelay { + fn default() -> Self { AssignmentsByDelay(Default::default()) } +} +impl Default for AssignmentsByDelay { + fn default() -> Self { AssignmentsByDelay(Default::default()) } +} + +impl AssignmentsByDelay +where C: Criteria, Assignment: Position, +{ + /// Add new `Assignment` avoiding inserting any duplicates. + /// + /// Assumes there is only one valid delay value determined by + /// some VRF output. + pub(super) fn insert_assignment_unchecked(&mut self, a: Assignment, context: &ApprovalContext) -> DelayTranche { + let delay_tranche = a.delay_tranche(context); + let mut v = self.0.entry(delay_tranche).or_insert(Vec::new()); + v.push(a); + delay_tranche + } + + /// Iterate immutably over all assignments within the given tranche assignment range. + fn range(&self, r: R) -> impl Iterator> + where R: ::std::ops::RangeBounds, + { + self.0.range(r).map( |(_,v)| v.iter() ).flatten() + } +} + +impl AssignmentsByDelay +where C: Criteria, Assignment: Position, +{ + /// Iterate over all checkers, and their actual recieved times, + /// in the given tranche assignment, always earlier than the + /// recieved time. + fn iter_checker_n_recieved(&self, tranche: DelayTranche) + -> impl Iterator + '_ + { + // We use `btree_map::Range` as a hack to handle `tranche` being invalid well. + self.range(tranche..tranche+1).map( |a| a.checker_n_recieved() ) + } + + /// Add new `Assignment` avoiding inserting any duplicates. + /// + /// Assumes there is only one valid delay value determined by + /// some VRF output. + pub(super) fn insert_assignment_checked(&mut self, a: Assignment, context: &ApprovalContext) -> AssignmentResult { + let delay_tranche = a.delay_tranche(context); + let mut v = self.0.entry(delay_tranche).or_insert(Vec::new()); + // We could improve performance here with `HashMap` + // but these buckets should stay small-ish due to using VRFs. + if v.iter().any( |a0| a0.checker() == a.checker() ) { + return Err(Error::BadAssignment("Attempted inserting duplicate assignment!")); + } + // debug_assert!( !v.iter().any( |a0| a0.checker() == a.checker() ) ); + v.push(a); + Ok(delay_tranche) + } +} + +impl AssignmentsByDelay +where C: Criteria, Assignment: Position, +{ + pub fn drain_filter<'a,R,F>(&'a mut self, r: R, mut f: F) + -> impl Iterator> + 'a + where + R: ::std::ops::RangeBounds, + F: 'a + FnMut(&Assignment) -> bool, + { + self.0.range_mut(r).map( move |(_,selfy)| { + // https://github.com/rust-lang/rust/pull/43245#issuecomment-319188468 + let len = selfy.len(); + let mut del = 0; + { + let v = &mut **selfy; + + for i in 0..len { + if f(&v[i]) { + del += 1; + } else if del > 0 { + v.swap(i - del, i); + } + } + } + selfy.drain(len - del..) + } ).flatten() + } +} + + +/// Current status of a checker with an assignemnt to this candidate. +/// +/// We cannot store an `approved` state inside `AssignmentsByDelay` +/// because we maybe recieve approval messages before the assignment +/// message. We thus need some extra checker tracking data structure, +/// but more options exist: +/// +/// We could track an `Option` here, with `Some` for +/// assigned checkers, and `None` for approving, but unasigned, +/// but this complicates the code more than expected. +struct CheckerStatus { + /// Is this assignment approved? + approved: bool, + /// Is this my own assignment? + mine: bool, + // /// Improve lookup times, `None` if approved without existing assignment. + // delay_tranche: Option, +} + +/// All assignments tracked for one specfic parachain cadidate. +/// +/// TODO: Add some bitfield that detects multiple insertions by the same validtor. +#[derive(Default)] +pub struct CandidateTracker { + targets: ApprovalTargets, + /// Approval statments + checkers: HashMap, + /// Assignments of modulo type based on the relay chain VRF + /// + /// We only use `delay_tranche = 0` for `RelayVRFModulo` + /// but it's easier to reuse all this other code than + /// impement anything different. + relay_vrf_modulo: AssignmentsByDelay, + /// Assignments of delay type based on the relay chain VRF + relay_vrf_delay: AssignmentsByDelay, + /// Assignments of delay type based on candidate equivocations + relay_equivocation: AssignmentsByDelay, +} + +impl CandidateTracker { + fn access_criteria_mut(&mut self) -> &mut AssignmentsByDelay + where C: Criteria, Assignment: Position, + { + use core::any::Any; + (&mut self.relay_vrf_modulo as &mut dyn Any) + .downcast_mut::>() + .or( (&mut self.relay_vrf_delay as &mut dyn Any) + .downcast_mut::>() ) + .or( (&mut self.relay_equivocation as &mut dyn Any) + .downcast_mut::>() ) + .expect("Oops, we've some foreign type satisfying Criteria!") + } + + /// Read current approvals checkers target levels + pub fn targets(&self) -> &ApprovalTargets { &self.targets } + + // /// Write current approvals checkers target levels + // pub fn targets_mut(&self) -> &mut ApprovalTargets { &mut self.targets } + + /// Return whether the given validator approved this candiddate, + /// or `None` if we've no assignment form them. + pub fn is_approved_by_checker(&self, checker: &ValidatorId) -> Option { + self.checkers.get(checker).map(|status| status.approved) + } + + /// Mark validator as approving this candiddate + /// + /// We cannot expose approving my own candidates from the `Tracker` + /// because they require additional work. + pub(super) fn approve(&mut self, checker: ValidatorId, mine: bool) -> AssignmentResult<()> { + match self.checkers.entry(checker) { + Entry::Occupied(mut e) => { + let e = e.get_mut(); + if e.mine != mine { + return Err(Error::BadAssignment("Attempted to approve my own assignment from Tracker or visa versa!")); + } + e.approved = true; + }, + Entry::Vacant(mut e) => { + e.insert(CheckerStatus { approved: true, mine: false, }); + }, + } + Ok(()) + } + + /// Mark another validator as approving this candiddate + /// + /// We accept and correctly process premature approve calls, but + /// our current scheme makes counting approvals slightly slower. + /// We can optimize performance later with slightly more complex code. + /// + /// TODO: We should rejects approving your own assignments, except + /// we've a bug that invoking this on yourself before the assignment + /// exists creates an assignemnt with `mine = true`. + pub fn approve_others(&mut self, checker: ValidatorId) -> AssignmentResult<()> { + self.approve(checker, false) + } + + /// Returns the approved and absent counts for all validtors + /// given by the iterator. Ignores unassigned validators, which + /// makes results meaningless if you want them counted, but + /// this behavior makes sense assuming checkers contains every + /// validator discussed elsewhere, including ourselves. + fn assignee_counts(&self, iter: I, noshow_tranche: DelayTranche) -> Counts + where I: Iterator + { + let mut cm: HashMap = HashMap::new(); // Deduplicate iter + let mut assigned: u32 = 0; + for (checker,recieved) in iter { + if let Some(b) = self.is_approved_by_checker(&checker) { + assigned += 1; // Panics if more than u32::MAX = 4 billion validators. + if !b { + cm.entry(checker) + .and_modify(|r| { *r = min(*r,recieved) }) + .or_insert(recieved); + } + } // TODO: Internal error log? + } + let mut waiting = cm.len() as u32; + let noshows = cm.values().cloned().filter(|r: &u32| *r < noshow_tranche).count() as u32; + let approved = assigned - waiting; + waiting -= noshows; + debug_assert!( assigned == approved + waiting + noshows ); + Counts { approved, waiting, noshows, assigned } + } + + /// Returns the approved and absent counts of validtors assigned + /// by either `RelayVRFStory` or `RelayWquivocationStory`, and + /// within the given range. + fn count_assignees_in_tranche( + &self, + tranche: DelayTranche, + noshow_tranche: DelayTranche + ) -> Counts + { + use core::any::TypeId; + let s = TypeId::of::(); + if s == TypeId::of::() { + let x = self.relay_vrf_modulo.iter_checker_n_recieved(tranche); + let y = self.relay_vrf_delay.iter_checker_n_recieved(tranche); + self.assignee_counts( x.chain(y), noshow_tranche ) + } else if s == TypeId::of::() { + let z = self.relay_equivocation.iter_checker_n_recieved(tranche); + self.assignee_counts(z, noshow_tranche) + } else { panic!("Oops, we've some foreign type for Criteria::Story!") } + } + + /// Initialize `AssigneeStatus` tracker before any delay tranches applied + pub(super) fn init_assignee_status(&self) -> AssigneeStatus { + // We account for no shows in multiple tranches by increasing the no show timeout + AssigneeStatus { + tranche: 0, + min_tranche: 1, + target: self.targets.target::(), + approved: 0, + waiting: 0, + noshows: 0, + debt: 0, + assigned: 0, + noshow_timeout: self.targets.noshow_timeout, + } + } + + /// Advance `AssigneeStatus` tracker by one delay tranche, + /// but without exceeding the current tranche. + pub(super) fn advance_assignee_status(&self, now: DelayTranche, mut c: AssigneeStatus) + -> Option + { + // We stop if enough checkers were assigned and we've replaced + // any no shows and we've added a tranche per no show. + if c.assigned > c.target && c.debt == 0 && c.tranche >= c.min_tranche { return None; } + + // We do not count tranches for which we should not yet have + // recieved any assignments, even though we do store early + // announcements. + if c.tranche + c.noshow_timeout > now + self.targets.noshow_timeout { + return None; + } + // === while c.tranche + noshow_timeout <= now + self.targets.noshow_timeout + + let d = self.count_assignees_in_tranche::(c.tranche, c.noshow_timeout); + c.assigned += d.assigned; + c.waiting += d.waiting; + c.noshows += d.noshows; + c.debt += d.noshows; + c.approved += d.approved; + c.tranche += 1; + + // Consider later tranches if not enough assignees yet + if c.assigned <= c.target || c.tranche < c.min_tranche { + return Some(c); + // === continue; + } + // Ignore later tranches if we've enough assignees and no no shows. + // + if c.debt == 0 { + return Some(c); + // === break if self.tranche >= self.min_tranche or continue otherwise + } + // We replace no shows by increasing our target when + // reaching our initial or any subseuent target. + // We ask for at least one checkers per no show here, + // acording to the analysis (TODO: Alistair) + c.target = c.assigned; // + c.debt; // two? + c.min_tranche += c.tranche + c.debt; + c.debt = 0; + // We view tranches as later for being counted no show + // since they announced much latter. + c.noshow_timeout += self.targets.noshow_timeout; + + Some(c) + // === continue; + } + + /// Recompute our current approval progress numbers + pub fn assignee_status(&self, now: DelayTranche) -> AssigneeStatus { + let mut s = self.init_assignee_status::(); + while let Some(t) = self.advance_assignee_status::(now, s.clone()) { s = t; } + s + } + + pub fn is_approved_before(&self, now: DelayTranche) -> bool { + self.assignee_status::(now).is_approved() + && self.assignee_status::(now).is_approved() + } +} + +struct Counts { + /// Approval votes thus far + approved: u32, + /// Awaiting approval votes + waiting: u32, + /// We've waoted too long for these, so they require relacement + noshows: u32, + /// Total validtors assigned, so approved wiaitng, or noshow + assigned: u32 +} + + +/// Tracks approval checkers assignments +/// +/// Inner type and builder for `Watcher` and `Announcer`, which +/// provide critical methods unavailable on `Tracker` alone. +pub struct Tracker { + context: ApprovalContext, + pub(super) current_slot: u64, + pub(super) relay_vrf_story: stories::RelayVRFStory, + relay_equivocation_story: stories::RelayEquivocationStory, + candidates: HashMap +} + +impl Tracker { + /// Create a tracker from which we build a `Watcher` or `Announcer` + // TODO: Improve `stories::*::new()` methods + pub fn new(context: ApprovalContext, target: u16) -> AssignmentResult { + let current_slot = context.anv_slot_number(); + let relay_vrf_story = context.new_vrf_story() ?; + let relay_equivocation_story = context.new_equivocation_story(); + + let mut candidates = HashMap::new(); + for paraid in context.paraids() { + candidates.insert(paraid, CandidateTracker { + // TODO: We'll want more nuanced control over initial targets levels. + targets: ApprovalTargets::default(), + checkers: HashMap::new(), + relay_vrf_modulo: AssignmentsByDelay::default(), + relay_vrf_delay: AssignmentsByDelay::default(), + relay_equivocation: AssignmentsByDelay::default(), + } ); + } + + Ok(Tracker { context, current_slot, relay_vrf_story, relay_equivocation_story, candidates, }) + } + + pub fn context(&self) -> &ApprovalContext { &self.context } + + pub(super) fn access_story(&self) -> &C::Story + where C: Criteria, Assignment: Position, + { + use core::any::Any; + (&self.relay_vrf_story as &dyn Any).downcast_ref::() + .or( (&self.relay_equivocation_story as &dyn Any).downcast_ref::() ) + .expect("Oops, we've some foreign type as Criteria::Story!") + } + + /// Read individual candidate's tracker + /// + /// Useful for `targets` and maybe `is_approved_before` methods of `CandidateTracker`. + pub fn candidate(&self, paraid: &ParaId) -> AssignmentResult<&CandidateTracker> + { + self.candidates.get(paraid) + .ok_or(Error::BadAssignment("Absent ParaId")) + } + + /// Access individual candidate's tracker mutably + /// + /// Useful for `approve` method of `CandidateTracker`. + pub fn candidate_mut(&mut self, paraid: &ParaId) -> AssignmentResult<&mut CandidateTracker> { + self.candidates.get_mut(paraid) + .ok_or(Error::BadAssignment("Absent ParaId")) + } + + pub fn candidates(&self) -> impl Iterator + '_ { + self.candidates.iter() + } + + pub fn candidates_mut(&mut self) -> impl Iterator + '_ { + self.candidates.iter_mut() + } + + /// Insert assignment verified elsewhere + pub(super) fn insert_assignment(&mut self, a: Assignment, mine: bool) -> AssignmentResult<()> + where C: Criteria, Assignment: Position, + { + let checker = a.checker().clone(); + let paraid = a.paraid(&self.context) + .ok_or(Error::BadAssignment("Insert attempted on missing ParaId.")) ?; + // let candidate = self.candidate_mut(¶id); + let candidate = self.candidates.get_mut(¶id) + .ok_or(Error::BadAssignment("Absent ParaId")) ?; + // We must handle some duplicate assignments because checkers + // could be assigned under both RelayVRF* and RelayEquivocation + if let Some(cs) = candidate.checkers.get_mut(&checker) { + if cs.mine != mine { + return Err(Error::BadAssignment("Attempted inserting assignment with disagreement over it being mine!")); + } + } + candidate.access_criteria_mut::().insert_assignment_checked(a,&self.context) ?; + candidate.checkers.entry(checker).or_insert(CheckerStatus { approved: false, mine, }); + Ok(()) + } + + /// Verify an assignments signature without inserting + pub(super) fn verify_only(&self, a: &AssignmentSigned) + -> AssignmentResult> + where C: Criteria, Assignment: Position, + { + let (context,a) = a.verify(self.access_story::(), self.current_delay_tranche()) ?; + if *context != self.context { + return Err(Error::BadAssignment("Incorrect ApprovalContext")); + } + Ok(a) + } + + /// Insert an assignment after verifying its signature + pub(super) fn verify_and_insert( + &mut self, + a: &AssignmentSigned, + myself: Option) + -> AssignmentResult<()> + where C: Criteria, Assignment: Position, + { + if myself.as_ref() == Some(a.checker()) { + return Err(Error::BadAssignment("Attempted verification of my own ")); + } + let a = self.verify_only(a) ?; + self.insert_assignment(a,false) + } + + pub fn current_anv_slot(&self) -> u64 { self.current_slot } + + pub fn delay_tranche(&self, slot: u64) -> Option { + let slot = slot.checked_sub( self.context.anv_slot_number() ) ?; + u32::try_from( max(slot, self.context.num_delay_tranches() as u64 - 1) ).ok() + } + + pub fn current_delay_tranche(&self) -> DelayTranche { + self.delay_tranche( self.current_slot ) + .expect("We initialise current_slot to context.anv_slot_number and then always increased it afterwards, qed") + } + + /* + pub fn current_noshow_delay_tranche(&self) -> DelayTranche { + self.current_delay_tranche() + .saturating_sub( stories::NOSHOW_DELAY_TRANCHES ) + } + */ + + /// Ask if all candidates are approved + pub fn is_approved(&self) -> bool { + let now = self.current_delay_tranche(); + self.candidates.iter().all( |(_paraid,c)| c.is_approved_before(now) ) + } + + /// Initalize tracking others assignments and approvals + /// without creating assignments ourself. + pub fn into_watcher(self) -> Watcher { + Watcher { tracker: self } + } +} + + +/// Tracks only others assignments and approvals +pub struct Watcher { + tracker: Tracker, +} + +impl ops::Deref for Watcher { + type Target = Tracker; + fn deref(&self) -> &Tracker { &self.tracker } +} +impl ops::DerefMut for Watcher { + fn deref_mut(&mut self) -> &mut Tracker { &mut self.tracker } +} + +impl Watcher { + /// Advances the AnV slot aka time to the specified value. + pub fn advance_anv_slot(&mut self, slot: u64) { + self.tracker.current_slot = max(self.tracker.current_slot, slot); + } + + /// Insert an assignment notice after verifying its signature + pub fn import_others(&mut self, a: &[u8]) -> AssignmentResult<()> { + unimplemented!(); // deserialize + } + + // Major TODO: Add RelayEquivocations +} diff --git a/node/core/approvals/src/error.rs b/node/core/approvals/src/error.rs new file mode 100644 index 000000000000..b860f8919239 --- /dev/null +++ b/node/core/approvals/src/error.rs @@ -0,0 +1,32 @@ + +// use polkadot_primitives::v0::{ValidatorId, Hash}; + +/// Error type for validation +#[derive(Debug, derive_more::Display, derive_more::From)] +pub enum Error { + // /// Client error + // Client(sp_blockchain::Error), + // /// Consensus error + // Consensus(consensus::error::Error), + /// An I/O error. + Io(std::io::Error), + + /// Recieved an invalid approval checker assignment + #[display(fmt = "Recieved an invalid approval checker assignment: {}", _ +0)] + BadAssignment(&'static str), + /// Invalid story used for approval checker assignments + #[display(fmt = "Invalid story for approval checker assignments: {}", _0)] + BadStory(&'static str), +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + // Error::Client(ref err) => Some(err), + // Error::Consensus(ref err) => Some(err), + Error::Io(ref err) => Some(err), + _ => None, + } + } +} diff --git a/node/core/approvals/src/lib.rs b/node/core/approvals/src/lib.rs new file mode 100644 index 000000000000..954aaa2d6c8a --- /dev/null +++ b/node/core/approvals/src/lib.rs @@ -0,0 +1,5 @@ + +pub mod assignments; +mod error; + +pub use error::Error; \ No newline at end of file