From 21d108e545b645afe15b64dd0a82742137ac1191 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 10 Dec 2020 10:12:22 -0600 Subject: [PATCH 01/35] skeleton --- Cargo.toml | 1 + node/core/approval-voting/Cargo.toml | 16 ++++++++++++++++ node/core/approval-voting/src/lib.rs | 22 ++++++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 node/core/approval-voting/Cargo.toml create mode 100644 node/core/approval-voting/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 57708ded628e..debe7e7a3cca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ members = [ "xcm/xcm-builder", "xcm/xcm-executor", "node/collation-generation", + "node/core/approval-voting", "node/core/av-store", "node/core/backing", "node/core/bitfield-signing", diff --git a/node/core/approval-voting/Cargo.toml b/node/core/approval-voting/Cargo.toml new file mode 100644 index 000000000000..6f076d713fc8 --- /dev/null +++ b/node/core/approval-voting/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "polkadot-node-core-approval-voting" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2018" + +[dependencies] +futures = "0.3.8" + +polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" } +polkadot-overseer = { path = "../../overseer" } +polkadot-primitives = { path = "../../../primitives" } + +sc-service = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] \ No newline at end of file diff --git a/node/core/approval-voting/src/lib.rs b/node/core/approval-voting/src/lib.rs new file mode 100644 index 000000000000..bce74c8ff2c0 --- /dev/null +++ b/node/core/approval-voting/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! The Approval Voting Subsystem. +//! +//! This subsystem is responsible for determining candidates to do approval checks +//! on, performing those approval checks, and tracking the assignments and approvals +//! of others. It uses this information to determine when candidates and blocks have +//! been sufficiently approved to finalize. From 7c0a083fb843e077f7c59a377e62a1ecfbee7a0c Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 10 Dec 2020 19:33:32 -0600 Subject: [PATCH 02/35] skeleton aux-schema module --- node/core/approval-voting/src/aux_schema.rs | 31 +++++++++++++++++++++ node/core/approval-voting/src/lib.rs | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 node/core/approval-voting/src/aux_schema.rs diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs new file mode 100644 index 000000000000..1a4e286ac33f --- /dev/null +++ b/node/core/approval-voting/src/aux_schema.rs @@ -0,0 +1,31 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Auxiliary DB schema, accessors, and writers for on-disk persisted approval storage +//! data. +//! +//! We persist data to disk although it is not intended to be used across runs of the +//! program. This is because under medium to long periods of finality stalling, for whatever +//! reason that may be, the amount of data we'd need to keep would be potentially too large +//! for memory. +//! +//! With tens or hundreds of parachains, hundreds of validators, and parablocks +//! in every relay chain block, there can be a humongous amount of information to reference +//! at any given time. +//! +//! As such, we provide a function from this module to clear the database on start-up. +//! In the future, we may use a temporary DB which doesn't need to be wiped, but for the +//! time being we share the same DB with the rest of Substrate. diff --git a/node/core/approval-voting/src/lib.rs b/node/core/approval-voting/src/lib.rs index bce74c8ff2c0..60daf2776885 100644 --- a/node/core/approval-voting/src/lib.rs +++ b/node/core/approval-voting/src/lib.rs @@ -20,3 +20,5 @@ //! on, performing those approval checks, and tracking the assignments and approvals //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. + +mod aux_schema; From f1308f099241b738934ac93c9e95d6d85a63816d Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:16:42 -0600 Subject: [PATCH 03/35] start approval types --- Cargo.lock | 12 ++++++ node/primitives/Cargo.toml | 1 + node/primitives/src/approval.rs | 76 +++++++++++++++++++++++++++++++++ node/primitives/src/lib.rs | 2 + 4 files changed, 91 insertions(+) create mode 100644 node/primitives/src/approval.rs diff --git a/Cargo.lock b/Cargo.lock index f376b6b9e60d..7488fa2f04c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4912,6 +4912,17 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "polkadot-node-core-approval-voting" +version = "0.1.0" +dependencies = [ + "futures 0.3.8", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "sc-client-api", +] + [[package]] name = "polkadot-node-core-av-store" version = "0.1.0" @@ -5101,6 +5112,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-statement-table", + "schnorrkel", "sp-core", "sp-runtime", ] diff --git a/node/primitives/Cargo.toml b/node/primitives/Cargo.toml index b39162370347..db5ded11370b 100644 --- a/node/primitives/Cargo.toml +++ b/node/primitives/Cargo.toml @@ -12,3 +12,4 @@ polkadot-statement-table = { path = "../../statement-table" } parity-scale-codec = { version = "1.3.5", default-features = false, features = ["derive"] } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +schnorrkel = "0.9" \ No newline at end of file diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs new file mode 100644 index 000000000000..2409620bd532 --- /dev/null +++ b/node/primitives/src/approval.rs @@ -0,0 +1,76 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Types relevant for approval. + +pub use polkadot_primitives::v1::{AssignmentId, CoreIndex}; +pub use schnorrkel::vrf::{VRFOutput, VRFProof}; + +use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; + +/// We split +pub type DelayTranche = u32; + +/// Different kinds of input data or criteria that can prove a validator's assignment +/// to check a particular parachain. +pub enum AssignmentCertKind { + RelayVRFModulo { + sample: u32, + }, + RelayVRFDelay { + core_index: CoreIndex, + }, +} + +/// A certification of assignment. +pub struct AssignmentCert { + /// The criterion which is claimed to be met by this cert. + pub kind: AssignmentCertKind, + /// The VRF showing the criterion is met. + pub vrf: (VRFOutput, VRFProof), +} + +/// An assignment crt which refers to the candidate under which the assignment is +/// relevant by block hash. +pub struct IndirectAssignmentCert { + /// A block hash where the candidate appears. + pub block_hash: Hash, + /// The validator index. + pub validator: ValidatorIndex, + /// The cert itself. + pub cert: AssignmentCert, +} + +/// A vote of approval on a candidate. +pub struct ApprovalVote(pub CandidateHash); + +// An approval vote signed by some validator. +pub type SignedApprovalVote = Signed; + +/// A signed approval vote which references the candidate indirectly via the block. +/// +/// In practice, we have a look-up from block hash and candidate index to candidate hash, +/// so this can be transformed into a `SignedApprovalVote`. +pub struct IndirectSignedApprovalVote { + // A block hash where the candidate appears. + pub block_hash: Hash, + /// The index of the candidate in the list of candidates fully included as-of the block. + pub candidate_index: u32, + /// The validator index. + pub validator: ValidatorIndex, + /// The signature by the validator. + pub signature: ValidatorSignature, +} diff --git a/node/primitives/src/lib.rs b/node/primitives/src/lib.rs index 0ea2799daac4..0aeaa53aa2c9 100644 --- a/node/primitives/src/lib.rs +++ b/node/primitives/src/lib.rs @@ -41,6 +41,8 @@ use std::pin::Pin; pub use sp_core::traits::SpawnNamed; +mod approval; + /// A statement, where the candidate receipt is included in the `Seconded` variant. /// /// This is the committed candidate receipt instead of the bare candidate receipt. As such, From 6a6c5c0c57df71d4c6d789feed6fb1f1c3f43d18 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:16:52 -0600 Subject: [PATCH 04/35] start aux schema with aux store --- node/core/approval-voting/Cargo.toml | 2 +- node/core/approval-voting/src/aux_schema.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/node/core/approval-voting/Cargo.toml b/node/core/approval-voting/Cargo.toml index 6f076d713fc8..32c825d951de 100644 --- a/node/core/approval-voting/Cargo.toml +++ b/node/core/approval-voting/Cargo.toml @@ -11,6 +11,6 @@ polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsys polkadot-overseer = { path = "../../overseer" } polkadot-primitives = { path = "../../../primitives" } -sc-service = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } [dev-dependencies] \ No newline at end of file diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 1a4e286ac33f..b74098908b56 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -29,3 +29,9 @@ //! As such, we provide a function from this module to clear the database on start-up. //! In the future, we may use a temporary DB which doesn't need to be wiped, but for the //! time being we share the same DB with the rest of Substrate. + +use sc_client_api::backend::AuxStore; + +pub(crate) clear(&impl AuxStore) { + // TODO: [now] +} From 717306635f30f075d9a3360fced336f2010c9865 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:19:09 -0600 Subject: [PATCH 05/35] doc --- node/primitives/src/approval.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 2409620bd532..ac6adce99fc3 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -21,7 +21,8 @@ pub use schnorrkel::vrf::{VRFOutput, VRFProof}; use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; -/// We split +/// Validators assigning to check a particular candidate are split up into tranches. +/// Earlier tranches of validators check first, with later tranches serving as backup. pub type DelayTranche = u32; /// Different kinds of input data or criteria that can prove a validator's assignment From 006dfa739b342e5c2abcbbc01ceb9af997d99bd6 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:39:56 -0600 Subject: [PATCH 06/35] finish basic types --- node/primitives/src/approval.rs | 20 +++++++++-- node/primitives/src/lib.rs | 2 +- .../implementers-guide/src/types/approval.md | 36 ------------------- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index ac6adce99fc3..398234cd1e83 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -25,13 +25,29 @@ use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, Valid /// Earlier tranches of validators check first, with later tranches serving as backup. pub type DelayTranche = u32; +/// A static context used for all relay-vrf-modulo VRFs. +pub const RELAY_VRF_MODULO_CONTEXT: &str = "A&V MOD"; + +/// A static context used for all relay-vrf-delay VRFs. +pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; + /// Different kinds of input data or criteria that can prove a validator's assignment /// to check a particular parachain. pub enum AssignmentCertKind { + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with a sample number. + /// + /// The context used to produce bytes is [`RELAY_VRF_MODULO_CONTEXT`] RelayVRFModulo { + /// The sample number used in this cert. sample: u32, }, + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with the index of a particular core. + /// + /// The context is [`RELAY_VRF_DELAY_CONTEXT`] RelayVRFDelay { + /// The core index chosen in this cert. core_index: CoreIndex, }, } @@ -58,7 +74,7 @@ pub struct IndirectAssignmentCert { /// A vote of approval on a candidate. pub struct ApprovalVote(pub CandidateHash); -// An approval vote signed by some validator. +/// An approval vote signed by some validator. pub type SignedApprovalVote = Signed; /// A signed approval vote which references the candidate indirectly via the block. @@ -66,7 +82,7 @@ pub type SignedApprovalVote = Signed; /// In practice, we have a look-up from block hash and candidate index to candidate hash, /// so this can be transformed into a `SignedApprovalVote`. pub struct IndirectSignedApprovalVote { - // A block hash where the candidate appears. + /// A block hash where the candidate appears. pub block_hash: Hash, /// The index of the candidate in the list of candidates fully included as-of the block. pub candidate_index: u32, diff --git a/node/primitives/src/lib.rs b/node/primitives/src/lib.rs index 0aeaa53aa2c9..82ac5dd28e9a 100644 --- a/node/primitives/src/lib.rs +++ b/node/primitives/src/lib.rs @@ -41,7 +41,7 @@ use std::pin::Pin; pub use sp_core::traits::SpawnNamed; -mod approval; +pub mod approval; /// A statement, where the candidate receipt is included in the `Seconded` variant. /// diff --git a/roadmap/implementers-guide/src/types/approval.md b/roadmap/implementers-guide/src/types/approval.md index 5603d03aa659..2db89d3481b6 100644 --- a/roadmap/implementers-guide/src/types/approval.md +++ b/roadmap/implementers-guide/src/types/approval.md @@ -100,40 +100,4 @@ struct CheckedAssignmentCert { ```rust type DelayTranche = u32; -``` - -## RelayVRFStory - -Assignment criteria are based off of possible stories about the relay-chain block that included the candidate. More information on stories is available in [the informational page on approvals.](../protocol-approval.md#stories). - -```rust -/// A story based on the VRF that authorized the relay-chain block where the candidate was -/// included. -/// -/// VRF Context is "A&V RC-VRF" -struct RelayVRFStory(VRFInOut); -``` - -## RelayEquivocationStory - -```rust -/// A story based on the candidate hash itself. Should be used when a candidate is an -/// equivocation: when there are two relay-chain blocks with the same RelayVRFStory, but only -/// one contains the candidate. -/// -/// VRF Context is "A&V RC-EQUIV" -struct RelayEquivocationStory(Hash); -``` - -## ExecutionTimePair - -```rust -struct ExecutionTimePair { - // The absolute time in milliseconds that the validator claims to have taken - // with the block. - absolute: u32, - // The validator's believed ratio in execution time to the average, expressed as a fixed-point - // 16-bit unsigned integer with 8 bits before and after the point. - ratio: FixedU16, -} ``` \ No newline at end of file From 557b5180e4f7e694db52e46f082dc419c6bdfaf6 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:16:42 -0600 Subject: [PATCH 07/35] start approval types --- Cargo.lock | 12 ++++++ node/primitives/Cargo.toml | 1 + node/primitives/src/approval.rs | 76 +++++++++++++++++++++++++++++++++ node/primitives/src/lib.rs | 2 + 4 files changed, 91 insertions(+) create mode 100644 node/primitives/src/approval.rs diff --git a/Cargo.lock b/Cargo.lock index f376b6b9e60d..7488fa2f04c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4912,6 +4912,17 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "polkadot-node-core-approval-voting" +version = "0.1.0" +dependencies = [ + "futures 0.3.8", + "polkadot-node-subsystem", + "polkadot-overseer", + "polkadot-primitives", + "sc-client-api", +] + [[package]] name = "polkadot-node-core-av-store" version = "0.1.0" @@ -5101,6 +5112,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-statement-table", + "schnorrkel", "sp-core", "sp-runtime", ] diff --git a/node/primitives/Cargo.toml b/node/primitives/Cargo.toml index b39162370347..db5ded11370b 100644 --- a/node/primitives/Cargo.toml +++ b/node/primitives/Cargo.toml @@ -12,3 +12,4 @@ polkadot-statement-table = { path = "../../statement-table" } parity-scale-codec = { version = "1.3.5", default-features = false, features = ["derive"] } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } +schnorrkel = "0.9" \ No newline at end of file diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs new file mode 100644 index 000000000000..2409620bd532 --- /dev/null +++ b/node/primitives/src/approval.rs @@ -0,0 +1,76 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Types relevant for approval. + +pub use polkadot_primitives::v1::{AssignmentId, CoreIndex}; +pub use schnorrkel::vrf::{VRFOutput, VRFProof}; + +use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; + +/// We split +pub type DelayTranche = u32; + +/// Different kinds of input data or criteria that can prove a validator's assignment +/// to check a particular parachain. +pub enum AssignmentCertKind { + RelayVRFModulo { + sample: u32, + }, + RelayVRFDelay { + core_index: CoreIndex, + }, +} + +/// A certification of assignment. +pub struct AssignmentCert { + /// The criterion which is claimed to be met by this cert. + pub kind: AssignmentCertKind, + /// The VRF showing the criterion is met. + pub vrf: (VRFOutput, VRFProof), +} + +/// An assignment crt which refers to the candidate under which the assignment is +/// relevant by block hash. +pub struct IndirectAssignmentCert { + /// A block hash where the candidate appears. + pub block_hash: Hash, + /// The validator index. + pub validator: ValidatorIndex, + /// The cert itself. + pub cert: AssignmentCert, +} + +/// A vote of approval on a candidate. +pub struct ApprovalVote(pub CandidateHash); + +// An approval vote signed by some validator. +pub type SignedApprovalVote = Signed; + +/// A signed approval vote which references the candidate indirectly via the block. +/// +/// In practice, we have a look-up from block hash and candidate index to candidate hash, +/// so this can be transformed into a `SignedApprovalVote`. +pub struct IndirectSignedApprovalVote { + // A block hash where the candidate appears. + pub block_hash: Hash, + /// The index of the candidate in the list of candidates fully included as-of the block. + pub candidate_index: u32, + /// The validator index. + pub validator: ValidatorIndex, + /// The signature by the validator. + pub signature: ValidatorSignature, +} diff --git a/node/primitives/src/lib.rs b/node/primitives/src/lib.rs index 0ea2799daac4..0aeaa53aa2c9 100644 --- a/node/primitives/src/lib.rs +++ b/node/primitives/src/lib.rs @@ -41,6 +41,8 @@ use std::pin::Pin; pub use sp_core::traits::SpawnNamed; +mod approval; + /// A statement, where the candidate receipt is included in the `Seconded` variant. /// /// This is the committed candidate receipt instead of the bare candidate receipt. As such, From dc557991e336865b3500234e365a926ea81edc06 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:19:09 -0600 Subject: [PATCH 08/35] doc --- node/primitives/src/approval.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 2409620bd532..ac6adce99fc3 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -21,7 +21,8 @@ pub use schnorrkel::vrf::{VRFOutput, VRFProof}; use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; -/// We split +/// Validators assigning to check a particular candidate are split up into tranches. +/// Earlier tranches of validators check first, with later tranches serving as backup. pub type DelayTranche = u32; /// Different kinds of input data or criteria that can prove a validator's assignment From fe79b4fb3410325135ab3432cfe467652558eadc Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 11:39:56 -0600 Subject: [PATCH 09/35] finish basic types --- node/primitives/src/approval.rs | 20 +++++++++-- node/primitives/src/lib.rs | 2 +- .../implementers-guide/src/types/approval.md | 36 ------------------- 3 files changed, 19 insertions(+), 39 deletions(-) diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index ac6adce99fc3..398234cd1e83 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -25,13 +25,29 @@ use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, Valid /// Earlier tranches of validators check first, with later tranches serving as backup. pub type DelayTranche = u32; +/// A static context used for all relay-vrf-modulo VRFs. +pub const RELAY_VRF_MODULO_CONTEXT: &str = "A&V MOD"; + +/// A static context used for all relay-vrf-delay VRFs. +pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; + /// Different kinds of input data or criteria that can prove a validator's assignment /// to check a particular parachain. pub enum AssignmentCertKind { + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with a sample number. + /// + /// The context used to produce bytes is [`RELAY_VRF_MODULO_CONTEXT`] RelayVRFModulo { + /// The sample number used in this cert. sample: u32, }, + /// An assignment story based on the VRF that authorized the relay-chain block where the + /// candidate was included combined with the index of a particular core. + /// + /// The context is [`RELAY_VRF_DELAY_CONTEXT`] RelayVRFDelay { + /// The core index chosen in this cert. core_index: CoreIndex, }, } @@ -58,7 +74,7 @@ pub struct IndirectAssignmentCert { /// A vote of approval on a candidate. pub struct ApprovalVote(pub CandidateHash); -// An approval vote signed by some validator. +/// An approval vote signed by some validator. pub type SignedApprovalVote = Signed; /// A signed approval vote which references the candidate indirectly via the block. @@ -66,7 +82,7 @@ pub type SignedApprovalVote = Signed; /// In practice, we have a look-up from block hash and candidate index to candidate hash, /// so this can be transformed into a `SignedApprovalVote`. pub struct IndirectSignedApprovalVote { - // A block hash where the candidate appears. + /// A block hash where the candidate appears. pub block_hash: Hash, /// The index of the candidate in the list of candidates fully included as-of the block. pub candidate_index: u32, diff --git a/node/primitives/src/lib.rs b/node/primitives/src/lib.rs index 0aeaa53aa2c9..82ac5dd28e9a 100644 --- a/node/primitives/src/lib.rs +++ b/node/primitives/src/lib.rs @@ -41,7 +41,7 @@ use std::pin::Pin; pub use sp_core::traits::SpawnNamed; -mod approval; +pub mod approval; /// A statement, where the candidate receipt is included in the `Seconded` variant. /// diff --git a/roadmap/implementers-guide/src/types/approval.md b/roadmap/implementers-guide/src/types/approval.md index 5603d03aa659..2db89d3481b6 100644 --- a/roadmap/implementers-guide/src/types/approval.md +++ b/roadmap/implementers-guide/src/types/approval.md @@ -100,40 +100,4 @@ struct CheckedAssignmentCert { ```rust type DelayTranche = u32; -``` - -## RelayVRFStory - -Assignment criteria are based off of possible stories about the relay-chain block that included the candidate. More information on stories is available in [the informational page on approvals.](../protocol-approval.md#stories). - -```rust -/// A story based on the VRF that authorized the relay-chain block where the candidate was -/// included. -/// -/// VRF Context is "A&V RC-VRF" -struct RelayVRFStory(VRFInOut); -``` - -## RelayEquivocationStory - -```rust -/// A story based on the candidate hash itself. Should be used when a candidate is an -/// equivocation: when there are two relay-chain blocks with the same RelayVRFStory, but only -/// one contains the candidate. -/// -/// VRF Context is "A&V RC-EQUIV" -struct RelayEquivocationStory(Hash); -``` - -## ExecutionTimePair - -```rust -struct ExecutionTimePair { - // The absolute time in milliseconds that the validator claims to have taken - // with the block. - absolute: u32, - // The validator's believed ratio in execution time to the average, expressed as a fixed-point - // 16-bit unsigned integer with 8 bits before and after the point. - ratio: FixedU16, -} ``` \ No newline at end of file From 8c0cef17123d57471c3be1e4d1f4d9e038e6a1a7 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 12:09:17 -0600 Subject: [PATCH 10/35] write out schema types --- Cargo.lock | 3 + node/core/approval-voting/Cargo.toml | 3 + node/core/approval-voting/src/aux_schema.rs | 70 ++++++++++++++++++- node/core/approval-voting/src/lib.rs | 3 + node/primitives/src/approval.rs | 4 ++ .../src/node/approval/approval-voting.md | 1 - 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7488fa2f04c0..accf3b3b4170 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4916,11 +4916,14 @@ dependencies = [ name = "polkadot-node-core-approval-voting" version = "0.1.0" dependencies = [ + "bitvec", "futures 0.3.8", + "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives", "sc-client-api", + "sp-consensus-slots", ] [[package]] diff --git a/node/core/approval-voting/Cargo.toml b/node/core/approval-voting/Cargo.toml index 32c825d951de..ba5ee2f09b3e 100644 --- a/node/core/approval-voting/Cargo.toml +++ b/node/core/approval-voting/Cargo.toml @@ -10,7 +10,10 @@ futures = "0.3.8" polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } polkadot-primitives = { path = "../../../primitives" } +polkadot-node-primitives = { path = "../../primitives" } +bitvec = "0.17.4" sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } [dev-dependencies] \ No newline at end of file diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index b74098908b56..dd6258d16e88 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -31,7 +31,75 @@ //! time being we share the same DB with the rest of Substrate. use sc_client_api::backend::AuxStore; +use polkadot_node_primitives::approval::{DelayTranche, RelayVRF}; +use polkadot_primitives::v1::{ + ValidatorIndex, GroupIndex, CandidateReceipt, SessionIndex, CoreIndex, + BlockNumber, Hash, +}; +use sp_consensus_slots::SlotNumber; -pub(crate) clear(&impl AuxStore) { +use std::collections::HashMap; +use bitvec::vec::BitVec; + +use super::Tick; + +const STORED_BLOCKS_KEY: &[u8] = b"StoredBlock"; + +/// Metadata regarding a specific tranche of assignments for a specific candidate. +pub(crate) struct TrancheEntry { + tranche: DelayTranche, + // Assigned validators, and the instant we received their assignment, rounded + // to the nearest tick. + assignments: Vec<(ValidatorIndex, Tick)>, +} + +/// Metadata regarding approval of a particular candidate within the context of some +/// particular block. +pub(crate) struct ApprovalEntry { + tranches: Vec, + backing_group: GroupIndex, + // When the next wakeup for this entry should occur. This is either to + // check a no-show or to check if we need to broadcast an assignment. + next_wakeup: Tick, + our_assignment: Option, + // `n_validators` bits. + assignments: BitVec, + approved: bool, +} + +/// Metadata regarding approval of a particular candidate. +pub(crate) struct CandidateEntry { + candidate: CandidateReceipt, + session: SessionIndex, + // Assignments are based on blocks, so we need to track assignments separately + // based on the block we are looking at. + block_assignments: HashMap, + approvals: BitVec, +} + +/// Metadata regarding approval of a particular block, by way of approval of the +/// candidates contained within it. +pub(crate) struct BlockEntry { + block_hash: Hash, + session: SessionIndex, + slot: SlotNumber, + relay_vrf_story: RelayVRF, + // The candidates included as-of this block and the index of the core they are + // leaving. Sorted ascending by core index. + candidates: Vec<(CoreIndex, Hash)>, + // A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`. + // The i'th bit is `tru` iff the candidate has been approved in the context of this + // block. The block can be considered approved if the bitfield has all bits set to `true`. + approved_bitfield: BitVec, + children: Vec, +} + +/// A range from earliest..last block number stored within the DB. +pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); + +// TODO [now]: probably in lib.rs +pub(crate) struct OurAssignment { } + +pub(crate) fn clear(_: &impl AuxStore) { // TODO: [now] } diff --git a/node/core/approval-voting/src/lib.rs b/node/core/approval-voting/src/lib.rs index 60daf2776885..270afee617a7 100644 --- a/node/core/approval-voting/src/lib.rs +++ b/node/core/approval-voting/src/lib.rs @@ -22,3 +22,6 @@ //! been sufficiently approved to finalize. mod aux_schema; + +/// A base unit of time, starting from the unix epoch, split into half-second intervals. +type Tick = u64; diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 398234cd1e83..1ad8544d4fe2 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -31,6 +31,10 @@ pub const RELAY_VRF_MODULO_CONTEXT: &str = "A&V MOD"; /// A static context used for all relay-vrf-delay VRFs. pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; +/// random bytes derived from the VRF submitted within the block by the +/// block author as a credential and used as input to approval assignment criteria. +pub struct RelayVRF(pub [u8; 32]); + /// Different kinds of input data or criteria that can prove a validator's assignment /// to check a particular parachain. pub enum AssignmentCertKind { diff --git a/roadmap/implementers-guide/src/node/approval/approval-voting.md b/roadmap/implementers-guide/src/node/approval/approval-voting.md index d1addd207f16..43b24124553f 100644 --- a/roadmap/implementers-guide/src/node/approval/approval-voting.md +++ b/roadmap/implementers-guide/src/node/approval/approval-voting.md @@ -82,7 +82,6 @@ struct BlockEntry { // The i'th bit is `true` iff the candidate has been approved in the context of // this block. The block can be considered approved has all bits set to 1 approved_bitfield: Bitfield, - rotation_offset: GroupIndex, children: Vec, } From 5ec3cfbc7877629e6534b7a74b8a6f258ebcaca0 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 12:23:21 -0600 Subject: [PATCH 11/35] add debug and codec impls to approval types --- node/core/approval-voting/Cargo.toml | 2 ++ node/primitives/Cargo.toml | 2 +- node/primitives/src/approval.rs | 9 ++++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/node/core/approval-voting/Cargo.toml b/node/core/approval-voting/Cargo.toml index ba5ee2f09b3e..b6c361b53835 100644 --- a/node/core/approval-voting/Cargo.toml +++ b/node/core/approval-voting/Cargo.toml @@ -6,6 +6,7 @@ edition = "2018" [dependencies] futures = "0.3.8" +parity-scale-codec = { version = "1.3.5", default-features = false, features = ["bit-vec", "derive"] } polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../../subsystem" } polkadot-overseer = { path = "../../overseer" } @@ -15,5 +16,6 @@ bitvec = "0.17.4" sc-client-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-consensus-slots = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } [dev-dependencies] \ No newline at end of file diff --git a/node/primitives/Cargo.toml b/node/primitives/Cargo.toml index db5ded11370b..1ad69626fe97 100644 --- a/node/primitives/Cargo.toml +++ b/node/primitives/Cargo.toml @@ -12,4 +12,4 @@ polkadot-statement-table = { path = "../../statement-table" } parity-scale-codec = { version = "1.3.5", default-features = false, features = ["derive"] } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -schnorrkel = "0.9" \ No newline at end of file +sp-consensus-vrf = { git = "https://github.com/paritytech/substrate", branch = "master" } \ No newline at end of file diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 1ad8544d4fe2..d7ec7a336f30 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -17,9 +17,10 @@ //! Types relevant for approval. pub use polkadot_primitives::v1::{AssignmentId, CoreIndex}; -pub use schnorrkel::vrf::{VRFOutput, VRFProof}; +pub use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; +use parity_scale_codec::{Encode, Decode}; /// Validators assigning to check a particular candidate are split up into tranches. /// Earlier tranches of validators check first, with later tranches serving as backup. @@ -33,10 +34,12 @@ pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; /// random bytes derived from the VRF submitted within the block by the /// block author as a credential and used as input to approval assignment criteria. +#[derive(Debug, Clone, Encode, Decode)] pub struct RelayVRF(pub [u8; 32]); /// Different kinds of input data or criteria that can prove a validator's assignment /// to check a particular parachain. +#[derive(Debug, Clone, Encode, Decode)] pub enum AssignmentCertKind { /// An assignment story based on the VRF that authorized the relay-chain block where the /// candidate was included combined with a sample number. @@ -57,6 +60,7 @@ pub enum AssignmentCertKind { } /// A certification of assignment. +#[derive(Debug, Clone, Encode, Decode)] pub struct AssignmentCert { /// The criterion which is claimed to be met by this cert. pub kind: AssignmentCertKind, @@ -66,6 +70,7 @@ pub struct AssignmentCert { /// An assignment crt which refers to the candidate under which the assignment is /// relevant by block hash. +#[derive(Debug, Clone, Encode, Decode)] pub struct IndirectAssignmentCert { /// A block hash where the candidate appears. pub block_hash: Hash, @@ -76,6 +81,7 @@ pub struct IndirectAssignmentCert { } /// A vote of approval on a candidate. +#[derive(Debug, Clone, Encode, Decode)] pub struct ApprovalVote(pub CandidateHash); /// An approval vote signed by some validator. @@ -85,6 +91,7 @@ pub type SignedApprovalVote = Signed; /// /// In practice, we have a look-up from block hash and candidate index to candidate hash, /// so this can be transformed into a `SignedApprovalVote`. +#[derive(Debug, Clone, Encode, Decode)] pub struct IndirectSignedApprovalVote { /// A block hash where the candidate appears. pub block_hash: Hash, From f415724cd9fc94cc84a7507c46a72438f0718ed6 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 12:23:38 -0600 Subject: [PATCH 12/35] add debug and codec impls to approval types also add some key computation --- Cargo.lock | 4 +- node/core/approval-voting/src/aux_schema.rs | 44 ++++++++++++++++++--- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index accf3b3b4170..a29e514e6786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4918,11 +4918,13 @@ version = "0.1.0" dependencies = [ "bitvec", "futures 0.3.8", + "parity-scale-codec", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives", "sc-client-api", + "sp-blockchain", "sp-consensus-slots", ] @@ -5115,7 +5117,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-statement-table", - "schnorrkel", + "sp-consensus-vrf", "sp-core", "sp-runtime", ] diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index dd6258d16e88..6eacaa427961 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -34,18 +34,20 @@ use sc_client_api::backend::AuxStore; use polkadot_node_primitives::approval::{DelayTranche, RelayVRF}; use polkadot_primitives::v1::{ ValidatorIndex, GroupIndex, CandidateReceipt, SessionIndex, CoreIndex, - BlockNumber, Hash, + BlockNumber, Hash, CandidateHash, }; use sp_consensus_slots::SlotNumber; +use parity_scale_codec::{Encode, Decode}; -use std::collections::HashMap; +use std::collections::BTreeMap; use bitvec::vec::BitVec; use super::Tick; -const STORED_BLOCKS_KEY: &[u8] = b"StoredBlock"; +const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlock"; /// Metadata regarding a specific tranche of assignments for a specific candidate. +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct TrancheEntry { tranche: DelayTranche, // Assigned validators, and the instant we received their assignment, rounded @@ -55,6 +57,7 @@ pub(crate) struct TrancheEntry { /// Metadata regarding approval of a particular candidate within the context of some /// particular block. +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct ApprovalEntry { tranches: Vec, backing_group: GroupIndex, @@ -68,17 +71,19 @@ pub(crate) struct ApprovalEntry { } /// Metadata regarding approval of a particular candidate. +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct CandidateEntry { candidate: CandidateReceipt, session: SessionIndex, // Assignments are based on blocks, so we need to track assignments separately // based on the block we are looking at. - block_assignments: HashMap, + block_assignments: BTreeMap, approvals: BitVec, } /// Metadata regarding approval of a particular block, by way of approval of the /// candidates contained within it. +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct BlockEntry { block_hash: Hash, session: SessionIndex, @@ -86,7 +91,7 @@ pub(crate) struct BlockEntry { relay_vrf_story: RelayVRF, // The candidates included as-of this block and the index of the core they are // leaving. Sorted ascending by core index. - candidates: Vec<(CoreIndex, Hash)>, + candidates: Vec<(CoreIndex, CandidateHash)>, // A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`. // The i'th bit is `tru` iff the candidate has been approved in the context of this // block. The block can be considered approved if the bitfield has all bits set to `true`. @@ -95,11 +100,40 @@ pub(crate) struct BlockEntry { } /// A range from earliest..last block number stored within the DB. +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); // TODO [now]: probably in lib.rs +#[derive(Debug, Clone, Encode, Decode)] pub(crate) struct OurAssignment { } pub(crate) fn clear(_: &impl AuxStore) { // TODO: [now] } + +pub(crate) fn load_block_entry(_: &impl AuxStore) + -> sp_blockchain::Result> +{ + // TODO: [now] + Ok(None) +} + +fn block_entry_key(block_hash: &Hash) -> [u8; 46] { + const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(block_hash.as_ref()); + + key +} + +fn candidate_entry_key(block_hash: &Hash) -> [u8; 46] { + const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(block_hash.as_ref()); + + key +} From 1088ec326c480d9ca2fe23e58057ed6cbf5df289 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 12:23:21 -0600 Subject: [PATCH 13/35] add debug and codec impls to approval types --- Cargo.lock | 13 +------------ node/primitives/Cargo.toml | 2 +- node/primitives/src/approval.rs | 13 ++++++++++++- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 01267bc08084..7cc8e4b8cdb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4935,17 +4935,6 @@ dependencies = [ "tracing-futures", ] -[[package]] -name = "polkadot-node-core-approval-voting" -version = "0.1.0" -dependencies = [ - "futures 0.3.8", - "polkadot-node-subsystem", - "polkadot-overseer", - "polkadot-primitives", - "sc-client-api", -] - [[package]] name = "polkadot-node-core-av-store" version = "0.1.0" @@ -5135,7 +5124,7 @@ dependencies = [ "parity-scale-codec", "polkadot-primitives", "polkadot-statement-table", - "schnorrkel", + "sp-consensus-vrf", "sp-core", "sp-runtime", ] diff --git a/node/primitives/Cargo.toml b/node/primitives/Cargo.toml index db5ded11370b..1ad69626fe97 100644 --- a/node/primitives/Cargo.toml +++ b/node/primitives/Cargo.toml @@ -12,4 +12,4 @@ polkadot-statement-table = { path = "../../statement-table" } parity-scale-codec = { version = "1.3.5", default-features = false, features = ["derive"] } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -schnorrkel = "0.9" \ No newline at end of file +sp-consensus-vrf = { git = "https://github.com/paritytech/substrate", branch = "master" } \ No newline at end of file diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 398234cd1e83..d7ec7a336f30 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -17,9 +17,10 @@ //! Types relevant for approval. pub use polkadot_primitives::v1::{AssignmentId, CoreIndex}; -pub use schnorrkel::vrf::{VRFOutput, VRFProof}; +pub use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; +use parity_scale_codec::{Encode, Decode}; /// Validators assigning to check a particular candidate are split up into tranches. /// Earlier tranches of validators check first, with later tranches serving as backup. @@ -31,8 +32,14 @@ pub const RELAY_VRF_MODULO_CONTEXT: &str = "A&V MOD"; /// A static context used for all relay-vrf-delay VRFs. pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; +/// random bytes derived from the VRF submitted within the block by the +/// block author as a credential and used as input to approval assignment criteria. +#[derive(Debug, Clone, Encode, Decode)] +pub struct RelayVRF(pub [u8; 32]); + /// Different kinds of input data or criteria that can prove a validator's assignment /// to check a particular parachain. +#[derive(Debug, Clone, Encode, Decode)] pub enum AssignmentCertKind { /// An assignment story based on the VRF that authorized the relay-chain block where the /// candidate was included combined with a sample number. @@ -53,6 +60,7 @@ pub enum AssignmentCertKind { } /// A certification of assignment. +#[derive(Debug, Clone, Encode, Decode)] pub struct AssignmentCert { /// The criterion which is claimed to be met by this cert. pub kind: AssignmentCertKind, @@ -62,6 +70,7 @@ pub struct AssignmentCert { /// An assignment crt which refers to the candidate under which the assignment is /// relevant by block hash. +#[derive(Debug, Clone, Encode, Decode)] pub struct IndirectAssignmentCert { /// A block hash where the candidate appears. pub block_hash: Hash, @@ -72,6 +81,7 @@ pub struct IndirectAssignmentCert { } /// A vote of approval on a candidate. +#[derive(Debug, Clone, Encode, Decode)] pub struct ApprovalVote(pub CandidateHash); /// An approval vote signed by some validator. @@ -81,6 +91,7 @@ pub type SignedApprovalVote = Signed; /// /// In practice, we have a look-up from block hash and candidate index to candidate hash, /// so this can be transformed into a `SignedApprovalVote`. +#[derive(Debug, Clone, Encode, Decode)] pub struct IndirectSignedApprovalVote { /// A block hash where the candidate appears. pub block_hash: Hash, From 3ce7414ae48d21f023ca157364c3d1d8ff28b6cf Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 12:36:27 -0600 Subject: [PATCH 14/35] getters for block and candidate entries --- node/core/approval-voting/src/aux_schema.rs | 31 +++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 6eacaa427961..0629418b1ef9 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -111,11 +111,32 @@ pub(crate) fn clear(_: &impl AuxStore) { // TODO: [now] } -pub(crate) fn load_block_entry(_: &impl AuxStore) +/// Load a block entry from the aux store. +pub(crate) fn load_block_entry(store: &impl AuxStore, block_hash: &Hash) -> sp_blockchain::Result> { - // TODO: [now] - Ok(None) + match store.get_aux(&block_entry_key(block_hash))? { + None => Ok(None), + Some(raw) => BlockEntry::decode(&mut &raw[..]) + .map(Some) + .map_err(|e| sp_blockchain::Error::Storage( + format!("Failed to decode block entry in approvals: {:?}", e) + )), + } +} + +/// Load a candidate entry from the aux store. +pub(crate) fn load_candidate_entry(store: &impl AuxStore, candidate_hash: &CandidateHash) + -> sp_blockchain::Result> +{ + match store.get_aux(&candidate_entry_key(candidate_hash))? { + None => Ok(None), + Some(raw) => CandidateEntry::decode(&mut &raw[..]) + .map(Some) + .map_err(|e| sp_blockchain::Error::Storage( + format!("Failed to decode block entry in approvals: {:?}", e) + )), + } } fn block_entry_key(block_hash: &Hash) -> [u8; 46] { @@ -128,12 +149,12 @@ fn block_entry_key(block_hash: &Hash) -> [u8; 46] { key } -fn candidate_entry_key(block_hash: &Hash) -> [u8; 46] { +fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; let mut key = [0u8; 14 + 32]; key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX); - key[14..][..32].copy_from_slice(block_hash.as_ref()); + key[14..][..32].copy_from_slice(candidate_hash.0.as_ref()); key } From c9af5a08d07b15e5fd8d834796aa0f0d19b14267 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 17:23:04 -0600 Subject: [PATCH 15/35] grumbles --- node/core/approval-voting/src/aux_schema.rs | 162 ++++++++++++++++++++ node/primitives/Cargo.toml | 2 +- node/primitives/src/approval.rs | 5 +- 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 node/core/approval-voting/src/aux_schema.rs diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs new file mode 100644 index 000000000000..517bd19854e3 --- /dev/null +++ b/node/core/approval-voting/src/aux_schema.rs @@ -0,0 +1,162 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Auxiliary DB schema, accessors, and writers for on-disk persisted approval storage +//! data. +//! +//! We persist data to disk although it is not intended to be used across runs of the +//! program. This is because under medium to long periods of finality stalling, for whatever +//! reason that may be, the amount of data we'd need to keep would be potentially too large +//! for memory. +//! +//! With tens or hundreds of parachains, hundreds of validators, and parablocks +//! in every relay chain block, there can be a humongous amount of information to reference +//! at any given time. +//! +//! As such, we provide a function from this module to clear the database on start-up. +//! In the future, we may use a temporary DB which doesn't need to be wiped, but for the +//! time being we share the same DB with the rest of Substrate. + +use sc_client_api::backend::AuxStore; +use polkadot_node_primitives::approval::{DelayTranche, RelayVRF}; +use polkadot_primitives::v1::{ + ValidatorIndex, GroupIndex, CandidateReceipt, SessionIndex, CoreIndex, + BlockNumber, Hash, CandidateHash, +}; +use sp_consensus_slots::SlotNumber; +use parity_scale_codec::{Encode, Decode}; + +use std::collections::BTreeMap; +use bitvec::vec::BitVec; + +use super::Tick; + +const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlock"; + +/// Metadata regarding a specific tranche of assignments for a specific candidate. +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct TrancheEntry { + tranche: DelayTranche, + // Assigned validators, and the instant we received their assignment, rounded + // to the nearest tick. + assignments: Vec<(ValidatorIndex, Tick)>, +} + +/// Metadata regarding approval of a particular candidate within the context of some +/// particular block. +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct ApprovalEntry { + tranches: Vec, + backing_group: GroupIndex, + // When the next wakeup for this entry should occur. This is either to + // check a no-show or to check if we need to broadcast an assignment. + next_wakeup: Tick, + our_assignment: Option, + // `n_validators` bits. + assignments: BitVec, + approved: bool, +} + +/// Metadata regarding approval of a particular candidate. +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct CandidateEntry { + candidate: CandidateReceipt, + session: SessionIndex, + // Assignments are based on blocks, so we need to track assignments separately + // based on the block we are looking at. + block_assignments: BTreeMap, + approvals: BitVec, +} + +/// Metadata regarding approval of a particular block, by way of approval of the +/// candidates contained within it. +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct BlockEntry { + block_hash: Hash, + session: SessionIndex, + slot: SlotNumber, + relay_vrf_story: RelayVRF, + // The candidates included as-of this block and the index of the core they are + // leaving. Sorted ascending by core index. + candidates: Vec<(CoreIndex, CandidateHash)>, + // A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`. + // The i'th bit is `tru` iff the candidate has been approved in the context of this + // block. The block can be considered approved if the bitfield has all bits set to `true`. + approved_bitfield: BitVec, + children: Vec, +} + +/// A range from earliest..last block number stored within the DB. +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); + +// TODO [now]: probably in lib.rs +#[derive(Debug, Clone, Encode, Decode)] +pub(crate) struct OurAssignment { } + +pub(crate) fn clear(_: &impl AuxStore) { + // TODO: [now] +} + +/// Load a block entry from the aux store. +pub(crate) fn load_block_entry(store: &impl AuxStore, block_hash: &Hash) + -> sp_blockchain::Result> +{ + match store.get_aux(&block_entry_key(block_hash))? { + None => Ok(None), + Some(raw) => BlockEntry::decode(&mut &raw[..]) + .map(Some) + .map_err(|e| sp_blockchain::Error::Storage( + format!("Failed to decode block entry in approvals: {:?}", e) + )), + } +} + +/// Load a candidate entry from the aux store. +pub(crate) fn load_candidate_entry(store: &impl AuxStore, candidate_hash: &CandidateHash) + -> sp_blockchain::Result> +{ + match store.get_aux(&candidate_entry_key(candidate_hash))? { + None => Ok(None), + Some(raw) => CandidateEntry::decode(&mut &raw[..]) + .map(Some) + .map_err(|e| sp_blockchain::Error::Storage( + format!("Failed to decode block entry in approvals: {:?}", e) + )), + } +} + +/// Load a + +fn block_entry_key(block_hash: &Hash) -> [u8; 46] { + const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&BLOCK_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(block_hash.as_ref()); + + key +} + +fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { + const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; + + let mut key = [0u8; 14 + 32]; + key[0..14].copy_from_slice(&CANDIDATE_ENTRY_PREFIX); + key[14..][..32].copy_from_slice(candidate_hash.0.as_ref()); + + key +} diff --git a/node/primitives/Cargo.toml b/node/primitives/Cargo.toml index 1ad69626fe97..888b13f0d324 100644 --- a/node/primitives/Cargo.toml +++ b/node/primitives/Cargo.toml @@ -12,4 +12,4 @@ polkadot-statement-table = { path = "../../statement-table" } parity-scale-codec = { version = "1.3.5", default-features = false, features = ["derive"] } runtime_primitives = { package = "sp-runtime", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" } -sp-consensus-vrf = { git = "https://github.com/paritytech/substrate", branch = "master" } \ No newline at end of file +sp-consensus-vrf = { git = "https://github.com/paritytech/substrate", branch = "master" } diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index d7ec7a336f30..c86bddf55902 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -16,10 +16,11 @@ //! Types relevant for approval. -pub use polkadot_primitives::v1::{AssignmentId, CoreIndex}; pub use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; -use polkadot_primitives::v1::{CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature}; +use polkadot_primitives::v1::{ + CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature, AssignmentId, CoreIndex, +}; use parity_scale_codec::{Encode, Decode}; /// Validators assigning to check a particular candidate are split up into tranches. From 65f9fedff78203fbb1de76630ccaf8f65fe5463f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 17:24:26 -0600 Subject: [PATCH 16/35] remove unused AssignmentId --- node/primitives/src/approval.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index c86bddf55902..32b4e5af7051 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -19,7 +19,7 @@ pub use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use polkadot_primitives::v1::{ - CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature, AssignmentId, CoreIndex, + CandidateHash, Hash, ValidatorIndex, Signed, ValidatorSignature, CoreIndex, }; use parity_scale_codec::{Encode, Decode}; From 910982a262702e63945cc17c9c846eeac9334c5f Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 17:35:43 -0600 Subject: [PATCH 17/35] load_decode utility --- node/core/approval-voting/src/aux_schema.rs | 39 ++++++++++++--------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 102a7d3ea733..58b0cd462084 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -44,7 +44,7 @@ use bitvec::vec::BitVec; use super::Tick; -const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlock"; +const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; /// Metadata regarding a specific tranche of assignments for a specific candidate. #[derive(Debug, Clone, Encode, Decode)] @@ -111,35 +111,41 @@ pub(crate) fn clear(_: &impl AuxStore) { // TODO: [now] } -/// Load a block entry from the aux store. -pub(crate) fn load_block_entry(store: &impl AuxStore, block_hash: &Hash) - -> sp_blockchain::Result> +fn load_decode(store: &impl AuxStore, key: &[u8]) + -> sp_blockchain::Result> { - match store.get_aux(&block_entry_key(block_hash))? { + match store.get_aux(key)? { None => Ok(None), - Some(raw) => BlockEntry::decode(&mut &raw[..]) + Some(raw) => D::decode(&mut &raw[..]) .map(Some) .map_err(|e| sp_blockchain::Error::Storage( - format!("Failed to decode block entry in approvals: {:?}", e) + format!("Failed to decode item in approvals DB: {:?}", e) )), } } +/// Load the stored-blocks key from the state. +pub(crate) fn load_stored_blocks(store: &impl AuxStore) + -> sp_blockchain::Result> +{ + load_decode(store, STORED_BLOCKS_KEY) +} + +/// Load a block entry from the aux store. +pub(crate) fn load_block_entry(store: &impl AuxStore, block_hash: &Hash) + -> sp_blockchain::Result> +{ + load_decode(store, &block_entry_key(block_hash)) +} + /// Load a candidate entry from the aux store. pub(crate) fn load_candidate_entry(store: &impl AuxStore, candidate_hash: &CandidateHash) -> sp_blockchain::Result> { - match store.get_aux(&candidate_entry_key(candidate_hash))? { - None => Ok(None), - Some(raw) => CandidateEntry::decode(&mut &raw[..]) - .map(Some) - .map_err(|e| sp_blockchain::Error::Storage( - format!("Failed to decode block entry in approvals: {:?}", e) - )), - } + load_decode(store, &candidate_entry_key(candidate_hash)) } -/// Load a +/// The key a given block entry is stored under. fn block_entry_key(block_hash: &Hash) -> [u8; 46] { const BLOCK_ENTRY_PREFIX: [u8; 14] = *b"Approvals_blck"; @@ -150,6 +156,7 @@ fn block_entry_key(block_hash: &Hash) -> [u8; 46] { key } +/// The key a given candidate entry is stored under. fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { const CANDIDATE_ENTRY_PREFIX: [u8; 14] = *b"Approvals_cand"; From 76bcfacf56aa32158429bfb11d880f1561ade892 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 18:31:32 -0600 Subject: [PATCH 18/35] implement DB clearing --- node/core/approval-voting/src/aux_schema.rs | 64 ++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 58b0cd462084..f7f442db6ff8 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -107,8 +107,50 @@ pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); #[derive(Debug, Clone, Encode, Decode)] pub(crate) struct OurAssignment { } -pub(crate) fn clear(_: &impl AuxStore) { - // TODO: [now] +/// Clear the aux store of everything related to +pub(crate) fn clear(store: &impl AuxStore) + -> sp_blockchain::Result<()> +{ + let range = match load_stored_blocks(store)? { + None => return Ok(()), + Some(range) => range, + }; + + let mut visited_height_keys = Vec::new(); + let mut visited_block_keys = Vec::new(); + let mut visited_candidate_keys = Vec::new(); + + for i in range.0..range.1 { + let at_height = match load_blocks_at_height(store, i)? { + None => continue, // sanity, shouldn't happen. + Some(a) => a, + }; + + visited_height_keys.push(blocks_at_height_key(i)); + + for block_hash in at_height { + let block_entry = match load_block_entry(store, &block_hash)? { + None => continue, + Some(e) => e, + }; + + visited_block_keys.push(block_entry_key(&block_hash)); + + for &(_, candidate_hash) in &block_entry.candidates { + visited_candidate_keys.push(candidate_entry_key(&candidate_hash)); + } + } + } + + // unfortunately demands a `collect` because aux store wants `&&[u8]` for some reason. + let visited_keys_borrowed = visited_height_keys.iter().map(|x| &x[..]) + .chain(visited_block_keys.iter().map(|x| &x[..])) + .chain(visited_candidate_keys.iter().map(|x| &x[..])) + .collect::>(); + + store.insert_aux(&[], &visited_keys_borrowed); + + Ok(()) } fn load_decode(store: &impl AuxStore, key: &[u8]) @@ -131,6 +173,13 @@ pub(crate) fn load_stored_blocks(store: &impl AuxStore) load_decode(store, STORED_BLOCKS_KEY) } +/// Load a blocks-at-height entry for a given block number. +pub(crate) fn load_blocks_at_height(store: &impl AuxStore, block_number: BlockNumber) + -> sp_blockchain::Result>> +{ + load_decode(store, &blocks_at_height_key(block_number)) +} + /// Load a block entry from the aux store. pub(crate) fn load_block_entry(store: &impl AuxStore, block_hash: &Hash) -> sp_blockchain::Result> @@ -166,3 +215,14 @@ fn candidate_entry_key(candidate_hash: &CandidateHash) -> [u8; 46] { key } + +/// The key a set of block hashes corresponding to a block number is stored under. +fn blocks_at_height_key(block_number: BlockNumber) -> [u8; 16] { + const BLOCKS_AT_HEIGHT_PREFIX: [u8; 12] = *b"Approvals_at"; + + let mut key = [0u8; 12 + 4]; + key[0..12].copy_from_slice(&BLOCKS_AT_HEIGHT_PREFIX); + block_number.using_encoded(|s| key[12..16].copy_from_slice(s)); + + key +} From 9a04615ae77e3415cce2825d22778652547232ff Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Fri, 11 Dec 2020 19:40:23 -0600 Subject: [PATCH 19/35] function for adding new block entry to aux store --- node/core/approval-voting/src/aux_schema.rs | 142 ++++++++++++++++++-- 1 file changed, 130 insertions(+), 12 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index f7f442db6ff8..04a33624ab43 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -40,7 +40,7 @@ use sp_consensus_slots::SlotNumber; use parity_scale_codec::{Encode, Decode}; use std::collections::BTreeMap; -use bitvec::vec::BitVec; +use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0}; use super::Tick; @@ -66,7 +66,7 @@ pub(crate) struct ApprovalEntry { next_wakeup: Tick, our_assignment: Option, // `n_validators` bits. - assignments: BitVec, + assignments: BitVec, approved: bool, } @@ -78,7 +78,7 @@ pub(crate) struct CandidateEntry { // Assignments are based on blocks, so we need to track assignments separately // based on the block we are looking at. block_assignments: BTreeMap, - approvals: BitVec, + approvals: BitVec, } /// Metadata regarding approval of a particular block, by way of approval of the @@ -93,9 +93,9 @@ pub(crate) struct BlockEntry { // leaving. Sorted ascending by core index. candidates: Vec<(CoreIndex, CandidateHash)>, // A bitfield where the i'th bit corresponds to the i'th candidate in `candidates`. - // The i'th bit is `tru` iff the candidate has been approved in the context of this + // The i'th bit is `true` iff the candidate has been approved in the context of this // block. The block can be considered approved if the bitfield has all bits set to `true`. - approved_bitfield: BitVec, + approved_bitfield: BitVec, children: Vec, } @@ -121,10 +121,7 @@ pub(crate) fn clear(store: &impl AuxStore) let mut visited_candidate_keys = Vec::new(); for i in range.0..range.1 { - let at_height = match load_blocks_at_height(store, i)? { - None => continue, // sanity, shouldn't happen. - Some(a) => a, - }; + let at_height = load_blocks_at_height(store, i)?; visited_height_keys.push(blocks_at_height_key(i)); @@ -148,7 +145,7 @@ pub(crate) fn clear(store: &impl AuxStore) .chain(visited_candidate_keys.iter().map(|x| &x[..])) .collect::>(); - store.insert_aux(&[], &visited_keys_borrowed); + store.insert_aux(&[], &visited_keys_borrowed)?; Ok(()) } @@ -166,6 +163,127 @@ fn load_decode(store: &impl AuxStore, key: &[u8]) } } +/// Information about a new candidate necessary to instantiate the requisite +/// candidate and approval entries. +pub(crate) struct NewCandidateInfo { + candidate: CandidateReceipt, + backing_group: GroupIndex, + our_assignment: Option, +} + +/// Record a new block entry. +/// +/// This will update the blocks-at-height mapping, the stored block range, if necessary, +/// and add block and candidate entries. It will also add approval entries to existing +/// candidate entries and add this as a child of any block entry corresponding to the +/// parent hash. +/// +/// Has no effect if there is already an entry for the block or `candidate_info` returns +/// `None` for any of the candidates referenced by the block entry. +pub(crate) fn add_block_entry( + store: &impl AuxStore, + parent_hash: Hash, + number: BlockNumber, + entry: BlockEntry, + n_validators: usize, + candidate_info: impl Fn(&CandidateHash) -> Option, +) -> sp_blockchain::Result<()> { + let session = entry.session; + + let new_block_range = { + let new_range = match load_stored_blocks(store)? { + None => Some(StoredBlockRange(number, number + 1)), + Some(range) => if range.1 <= number { + Some(StoredBlockRange(range.0, number + 1)) + } else { + None + } + }; + + new_range.map(|n| (STORED_BLOCKS_KEY, n.encode())) + }; + + let updated_blocks_at = { + let mut blocks_at_height = load_blocks_at_height(store, number)?; + if blocks_at_height.contains(&entry.block_hash) { + // seems we already have a block entry for this block. nothing to do here. + return Ok(()) + } + + blocks_at_height.push(entry.block_hash); + (blocks_at_height_key(number), blocks_at_height.encode()) + }; + + let candidate_entry_updates = { + let mut updated_entries = Vec::with_capacity(entry.candidates.len()); + for &(_, ref candidate_hash) in &entry.candidates { + let NewCandidateInfo { + candidate, + backing_group, + our_assignment, + } = match candidate_info(candidate_hash) { + None => return Ok(()), + Some(info) => info, + }; + + let mut candidate_entry = load_candidate_entry(store, &candidate_hash)? + .unwrap_or_else(move || CandidateEntry { + candidate, + session, + block_assignments: BTreeMap::new(), + approvals: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators], + }); + + candidate_entry.block_assignments.insert( + entry.block_hash, + ApprovalEntry { + tranches: Vec::new(), + backing_group, + next_wakeup: 0, + our_assignment, + assignments: bitvec::bitvec![BitOrderLsb0, u8; 0; n_validators], + approved: false, + } + ); + + updated_entries.push( + (candidate_entry_key(&candidate_hash), candidate_entry.encode()) + ); + } + + updated_entries + }; + + let updated_parent = { + load_block_entry(store, &parent_hash)?.map(|mut e| { + e.children.push(entry.block_hash); + (block_entry_key(&parent_hash), e.encode()) + }) + }; + + let write_block_entry = (block_entry_key(&entry.block_hash), entry.encode()); + + // write: + // - new block range + // - updated blocks-at item + // - fresh and updated candidate entries + // - the parent block entry. + // - the block entry itself + + // Unfortunately have to collect because aux-store demands &(&[u8], &[u8]). + let all_keys_and_values: Vec<_> = new_block_range.as_ref().into_iter() + .map(|&(ref k, ref v)| (&k[..], &v[..])) + .chain(std::iter::once((&updated_blocks_at.0[..], &updated_blocks_at.1[..]))) + .chain(candidate_entry_updates.iter().map(|&(ref k, ref v)| (&k[..], &v[..]))) + .chain(std::iter::once((&write_block_entry.0[..], &write_block_entry.1[..]))) + .chain(updated_parent.as_ref().into_iter().map(|&(ref k, ref v)| (&k[..], &v[..]))) + .collect(); + + store.insert_aux(&all_keys_and_values, &[])?; + + Ok(()) +} + /// Load the stored-blocks key from the state. pub(crate) fn load_stored_blocks(store: &impl AuxStore) -> sp_blockchain::Result> @@ -175,9 +293,9 @@ pub(crate) fn load_stored_blocks(store: &impl AuxStore) /// Load a blocks-at-height entry for a given block number. pub(crate) fn load_blocks_at_height(store: &impl AuxStore, block_number: BlockNumber) - -> sp_blockchain::Result>> -{ + -> sp_blockchain::Result> { load_decode(store, &blocks_at_height_key(block_number)) + .map(|x| x.unwrap_or_default()) } /// Load a block entry from the aux store. From bc6dcc2fd4e1d6dccbba946d6b6a2f7812fb1762 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 15 Dec 2020 16:38:26 -0500 Subject: [PATCH 20/35] start `canonicalize` implementation --- node/core/approval-voting/src/aux_schema.rs | 36 +++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 04a33624ab43..b84e831cf168 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -39,7 +39,7 @@ use polkadot_primitives::v1::{ use sp_consensus_slots::SlotNumber; use parity_scale_codec::{Encode, Decode}; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0}; use super::Tick; @@ -107,7 +107,39 @@ pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); #[derive(Debug, Clone, Encode, Decode)] pub(crate) struct OurAssignment { } -/// Clear the aux store of everything related to +/// Canonicalize some particular block, pruning everything before it and +/// pruning any competing branches at the same height. +pub(crate) fn canonicalize( + store: &impl AuxStore, + canon_number: BlockNumber, + canon_hash: BlockNumber, +) + -> sp_blockchain::Result<()> +{ + let range = match load_stored_blocks(store)? { + None => return Ok(()), + Some(range) if range.0 >= canon_number => return Ok(()). + Some(range) => range, + }; + + let mut visited_height_keys = Vec::new(); + let mut visited_block_keys = Vec::new(); + let mut visited_candidates = HashMap::new(); + + // TODO [now]: finish implementation + for i in range.0..canon_number { + let at_height = load_blocks_at_height(store, i)?; + + visited_height_keys.push(blocks_at_height_key(i)); + } + + { + let at_height = load_blocks_at_height(store, canon_number)?; + visited_height_keys.push(blocks_at_height_key(canon_number)); + } +} + +/// Clear the aux store of everything. pub(crate) fn clear(store: &impl AuxStore) -> sp_blockchain::Result<()> { From 834d776e733514eddbf2959a110c43177e8bc790 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Thu, 17 Dec 2020 11:19:27 -0500 Subject: [PATCH 21/35] more skeleton --- node/core/approval-voting/src/aux_schema.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index b84e831cf168..68c5cd4882b3 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -126,17 +126,32 @@ pub(crate) fn canonicalize( let mut visited_block_keys = Vec::new(); let mut visited_candidates = HashMap::new(); - // TODO [now]: finish implementation + let visit_and_remove_block_entry + + // First visit everything before the height. for i in range.0..canon_number { let at_height = load_blocks_at_height(store, i)?; visited_height_keys.push(blocks_at_height_key(i)); + + } + // Then visit everything at the height. { let at_height = load_blocks_at_height(store, canon_number)?; visited_height_keys.push(blocks_at_height_key(canon_number)); } + + // Follow all children of non-canonicalized blocks. + { + + } + + // Update all `CandidateEntry`s, deleting all those which now have empty `block_assignments`. + { + + } } /// Clear the aux store of everything. From aebfecce6241c0426683677a070cc04c37e0bf44 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 15:05:45 -0500 Subject: [PATCH 22/35] finish implementing canonicalize --- node/core/approval-voting/src/aux_schema.rs | 160 ++++++++++++++++-- .../src/node/approval/approval-voting.md | 2 +- 2 files changed, 146 insertions(+), 16 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index 68c5cd4882b3..fd9ed1ab45fb 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -40,6 +40,7 @@ use sp_consensus_slots::SlotNumber; use parity_scale_codec::{Encode, Decode}; use std::collections::{BTreeMap, HashMap}; +use std::collections::hash_map::Entry; use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0}; use super::Tick; @@ -112,46 +113,175 @@ pub(crate) struct OurAssignment { } pub(crate) fn canonicalize( store: &impl AuxStore, canon_number: BlockNumber, - canon_hash: BlockNumber, + canon_hash: Hash, ) -> sp_blockchain::Result<()> { let range = match load_stored_blocks(store)? { None => return Ok(()), - Some(range) if range.0 >= canon_number => return Ok(()). - Some(range) => range, + Some(range) => if range.0 >= canon_number { + return Ok(()) + } else { + range + }, }; - let mut visited_height_keys = Vec::new(); - let mut visited_block_keys = Vec::new(); + let mut deleted_height_keys = Vec::new(); + let mut deleted_block_keys = Vec::new(); + + // Storing all candidates in memory is potentially heavy, but should be fine + // as long as finality doesn't stall for a long while. We could optimize this + // by keeping only the metadata about which blocks reference each candidate. let mut visited_candidates = HashMap::new(); - let visit_and_remove_block_entry + // All the block heights we visited but didn't necessarily delete everything from. + let mut visited_heights = HashMap::new(); + + let visit_and_remove_block_entry = | + block_hash: Hash, + deleted_block_keys: &mut Vec<_>, + visited_candidates: &mut HashMap, + | -> sp_blockchain::Result> { + let block_entry = match load_block_entry(store, &block_hash)? { + None => return Ok(Vec::new()), + Some(b) => b, + }; - // First visit everything before the height. - for i in range.0..canon_number { - let at_height = load_blocks_at_height(store, i)?; + deleted_block_keys.push(block_entry_key(&block_hash)); + for &(_, ref candidate_hash) in &block_entry.candidates { + let candidate = match visited_candidates.entry(*candidate_hash) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => { + e.insert(match load_candidate_entry(store, candidate_hash)? { + None => continue, // Should not happen except for corrupt DB + Some(c) => c, + }) + } + }; - visited_height_keys.push(blocks_at_height_key(i)); + candidate.block_assignments.remove(&block_hash); + } + Ok(block_entry.children) + }; + // First visit everything before the height. + for i in range.0..canon_number { + let at_height = load_blocks_at_height(store, i)?; + deleted_height_keys.push(blocks_at_height_key(i)); + + for b in at_height { + let _ = visit_and_remove_block_entry( + b, + &mut deleted_block_keys, + &mut visited_candidates, + )?; + } } // Then visit everything at the height. - { + let pruned_branches = { let at_height = load_blocks_at_height(store, canon_number)?; - visited_height_keys.push(blocks_at_height_key(canon_number)); - } + deleted_height_keys.push(blocks_at_height_key(canon_number)); + + // Note that while there may be branches descending from blocks at earlier heights, + // we have already covered them by removing everything at earlier heights. + let mut pruned_branches = Vec::new(); + + for b in at_height { + let children = visit_and_remove_block_entry( + b, + &mut deleted_block_keys, + &mut visited_candidates, + )?; + + if b != canon_hash { + pruned_branches.extend(children); + } + } + + pruned_branches + }; // Follow all children of non-canonicalized blocks. { + let mut frontier: Vec<_> = pruned_branches.into_iter().map(|h| (canon_number + 1, h)).collect(); + while let Some((height, next_child)) = frontier.pop() { + let children = visit_and_remove_block_entry( + next_child, + &mut deleted_block_keys, + &mut visited_candidates, + )?; + + // extend the frontier of branches to include the given height. + frontier.extend(children.into_iter().map(|h| (height + 1, h))); + + // visit the at-height key for this deleted block's height. + let at_height = match visited_heights.entry(height) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => e.insert(load_blocks_at_height(store, height)?), + }; + if let Some(i) = at_height.iter().position(|x| x == &next_child) { + at_height.remove(i); + } + } } // Update all `CandidateEntry`s, deleting all those which now have empty `block_assignments`. - { + let (written_candidates, deleted_candidates) = { + let mut written = Vec::new(); + let mut deleted = Vec::new(); - } + for (candidate_hash, candidate) in visited_candidates { + if candidate.block_assignments.is_empty() { + deleted.push(candidate_entry_key(&candidate_hash)); + } else { + written.push((candidate_entry_key(&candidate_hash), candidate.encode())); + } + } + + (written, deleted) + }; + + // Update all blocks-at-height keys, deleting all those which now have empty `block_assignments`. + let written_at_height = { + visited_heights.into_iter().filter_map(|(h, at)| { + if at.is_empty() { + deleted_height_keys.push(blocks_at_height_key(h)); + None + } else { + Some((blocks_at_height_key(h), at.encode())) + } + }).collect::>() + }; + + // due to the fork pruning, this range actually might go too far above where our actual highest block is, + // if a relatively short fork is canonicalized. + let new_range = StoredBlockRange( + canon_number + 1, + std::cmp::max(range.1, canon_number + 1), + ).encode(); + + // Because aux-store requires &&[u8], we have to collect. + + let inserted_keys: Vec<_> = std::iter::once((&STORED_BLOCKS_KEY[..], &new_range[..])) + .chain(written_candidates.iter().map(|&(ref k, ref v)| (&k[..], &v[..]))) + .chain(written_at_height.iter().map(|&(ref k, ref v)| (&k[..], &v[..]))) + .collect(); + + let deleted_keys: Vec<_> = deleted_block_keys.iter().map(|k| &k[..]) + .chain(deleted_height_keys.iter().map(|k| &k[..])) + .chain(deleted_candidates.iter().map(|k| &k[..])) + .collect(); + + // Update the values on-disk. + store.insert_aux( + inserted_keys.iter(), + deleted_keys.iter(), + )?; + + Ok(()) } /// Clear the aux store of everything. diff --git a/roadmap/implementers-guide/src/node/approval/approval-voting.md b/roadmap/implementers-guide/src/node/approval/approval-voting.md index af961c3d6987..1c673f0324ef 100644 --- a/roadmap/implementers-guide/src/node/approval/approval-voting.md +++ b/roadmap/implementers-guide/src/node/approval/approval-voting.md @@ -142,7 +142,7 @@ Main loop: #### `OverseerSignal::BlockFinalized` -On receiving an `OverseerSignal::BlockFinalized(h)`, we fetch the block number `b` of that block from the ChainApi subsystem. We update our `StoredBlockRange` to begin at `b+1`. Additionally, we remove all block entries and candidates referenced by them up to and including `b`. Lastly, we prune out all descendents of `h` transitively: when we remove a `BlockEntry` with number `b` that is not equal to `h`, we recursively delete all the `BlockEntry`s referenced as children. We remove the `block_assignments` entry for the block hash and if `block_assignments` is now empty, remove the `CandidateEntry`. +On receiving an `OverseerSignal::BlockFinalized(h)`, we fetch the block number `b` of that block from the ChainApi subsystem. We update our `StoredBlockRange` to begin at `b+1`. Additionally, we remove all block entries and candidates referenced by them up to and including `b`. Lastly, we prune out all descendents of `h` transitively: when we remove a `BlockEntry` with number `b` that is not equal to `h`, we recursively delete all the `BlockEntry`s referenced as children. We remove the `block_assignments` entry for the block hash and if `block_assignments` is now empty, remove the `CandidateEntry`. We also update each of the `BlockNumber -> Vec` keys in the database to reflect the blocks at that height, clearing if empty. #### `OverseerSignal::ActiveLeavesUpdate` From 7faa0c31a5b581c6dc66a4dd3adebc1173d94bbc Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 15:18:12 -0500 Subject: [PATCH 23/35] tag TODO --- node/core/approval-voting/src/aux_schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema.rs index fd9ed1ab45fb..d2c6270d74ad 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema.rs @@ -104,7 +104,7 @@ pub(crate) struct BlockEntry { #[derive(Debug, Clone, Encode, Decode)] pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); -// TODO [now]: probably in lib.rs +// TODO https://github.com/paritytech/polkadot/1975: probably in lib.rs #[derive(Debug, Clone, Encode, Decode)] pub(crate) struct OurAssignment { } From 4a49ffbf46c7181d17673125ac29ae4326fe1880 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 15:32:23 -0500 Subject: [PATCH 24/35] implement a test AuxStore --- .../src/{aux_schema.rs => aux_schema/mod.rs} | 3 ++ .../approval-voting/src/aux_schema/tests.rs | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+) rename node/core/approval-voting/src/{aux_schema.rs => aux_schema/mod.rs} (99%) create mode 100644 node/core/approval-voting/src/aux_schema/tests.rs diff --git a/node/core/approval-voting/src/aux_schema.rs b/node/core/approval-voting/src/aux_schema/mod.rs similarity index 99% rename from node/core/approval-voting/src/aux_schema.rs rename to node/core/approval-voting/src/aux_schema/mod.rs index d2c6270d74ad..bee5d7e3b5f4 100644 --- a/node/core/approval-voting/src/aux_schema.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -45,6 +45,9 @@ use bitvec::{vec::BitVec, order::Lsb0 as BitOrderLsb0}; use super::Tick; +#[cfg(test)] +mod tests; + const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; /// Metadata regarding a specific tranche of assignments for a specific candidate. diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs new file mode 100644 index 000000000000..b4f212540c87 --- /dev/null +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -0,0 +1,48 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Tests for the aux-schema of approval voting. + +use super::*; +use std::cell::RefCell; + +#[derive(Default)] +struct TestStore { + inner: RefCell, Vec>>, +} + +impl AuxStore for TestStore { + fn insert_aux<'a, 'b: 'a, 'c: 'a, I, D>(&self, insertions: I, deletions: D) -> sp_blockchain::Result<()> + where I: IntoIterator, D: IntoIterator + { + let mut store = self.inner.borrow_mut(); + + // insertions before deletions. + for (k, v) in insertions { + store.insert(k.to_vec(), v.to_vec()); + } + + for k in deletions { + store.remove(&k[..]); + } + + Ok(()) + } + + fn get_aux(&self, key: &[u8]) -> sp_blockchain::Result>> { + Ok(self.inner.borrow().get(key).map(|v| v.clone())) + } +} From 0f1a8d66236022b796b2b0391028c5d274098d44 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 15:33:02 -0500 Subject: [PATCH 25/35] add allow(unused) --- node/core/approval-voting/src/aux_schema/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index bee5d7e3b5f4..bb3db36afbc2 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -30,6 +30,9 @@ //! In the future, we may use a temporary DB which doesn't need to be wiped, but for the //! time being we share the same DB with the rest of Substrate. +// TODO https://github.com/paritytech/polkadot/1975: remove this +#![allow(unused)] + use sc_client_api::backend::AuxStore; use polkadot_node_primitives::approval::{DelayTranche, RelayVRF}; use polkadot_primitives::v1::{ From f7d7c4910188ab0d6152f77fb3729ee1f16a9a87 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 17:43:54 -0500 Subject: [PATCH 26/35] basic loading and deleting test --- .../approval-voting/src/aux_schema/mod.rs | 12 +-- .../approval-voting/src/aux_schema/tests.rs | 93 +++++++++++++++++++ node/primitives/src/approval.rs | 2 +- 3 files changed, 100 insertions(+), 7 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index bb3db36afbc2..beb20289aa6b 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -54,7 +54,7 @@ mod tests; const STORED_BLOCKS_KEY: &[u8] = b"Approvals_StoredBlocks"; /// Metadata regarding a specific tranche of assignments for a specific candidate. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct TrancheEntry { tranche: DelayTranche, // Assigned validators, and the instant we received their assignment, rounded @@ -64,7 +64,7 @@ pub(crate) struct TrancheEntry { /// Metadata regarding approval of a particular candidate within the context of some /// particular block. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct ApprovalEntry { tranches: Vec, backing_group: GroupIndex, @@ -78,7 +78,7 @@ pub(crate) struct ApprovalEntry { } /// Metadata regarding approval of a particular candidate. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct CandidateEntry { candidate: CandidateReceipt, session: SessionIndex, @@ -90,7 +90,7 @@ pub(crate) struct CandidateEntry { /// Metadata regarding approval of a particular block, by way of approval of the /// candidates contained within it. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct BlockEntry { block_hash: Hash, session: SessionIndex, @@ -107,11 +107,11 @@ pub(crate) struct BlockEntry { } /// A range from earliest..last block number stored within the DB. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); // TODO https://github.com/paritytech/polkadot/1975: probably in lib.rs -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct OurAssignment { } /// Canonicalize some particular block, pruning everything before it and diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index b4f212540c87..db5ce7e53e45 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -46,3 +46,96 @@ impl AuxStore for TestStore { Ok(self.inner.borrow().get(key).map(|v| v.clone())) } } + +impl TestStore { + fn write_stored_blocks(&self, range: StoredBlockRange) { + self.inner.borrow_mut().insert( + STORED_BLOCKS_KEY.to_vec(), + range.encode(), + ); + } + + fn write_blocks_at_height(&self, height: BlockNumber, blocks: &[Hash]) { + self.inner.borrow_mut().insert( + blocks_at_height_key(height).to_vec(), + blocks.encode(), + ); + } + + fn write_block_entry(&self, block_hash: &Hash, entry: &BlockEntry) { + self.inner.borrow_mut().insert( + block_entry_key(block_hash).to_vec(), + entry.encode(), + ); + } + + fn write_candidate_entry(&self, candidate_hash: &CandidateHash, entry: &CandidateEntry) { + self.inner.borrow_mut().insert( + candidate_entry_key(candidate_hash).to_vec(), + entry.encode(), + ); + } +} + +#[test] +fn read_write() { + let store = TestStore::default(); + + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + let candidate_hash = CandidateHash(Hash::repeat_byte(3)); + + let range = StoredBlockRange(10, 20); + let at_height = vec![hash_a, hash_b]; + + let block_entry = BlockEntry { + block_hash: hash_a, + session: 5, + slot: 10, + relay_vrf_story: RelayVRF([0; 32]), + candidates: vec![(CoreIndex(0), candidate_hash)], + approved_bitfield: Default::default(), + children: Vec::new(), + }; + + let candidate_entry = CandidateEntry { + candidate: Default::default(), + session: 5, + block_assignments: vec![ + (hash_a, ApprovalEntry { + tranches: Vec::new(), + backing_group: GroupIndex(1), + next_wakeup: 1000, + our_assignment: None, + assignments: Default::default(), + approved: false, + }) + ].into_iter().collect(), + approvals: Default::default(), + }; + + store.write_stored_blocks(range.clone()); + store.write_blocks_at_height(1, &at_height); + store.write_block_entry(&hash_a, &block_entry); + store.write_candidate_entry(&candidate_hash, &candidate_entry); + + assert_eq!(load_stored_blocks(&store).unwrap(), Some(range)); + assert_eq!(load_blocks_at_height(&store, 1).unwrap(), at_height); + assert_eq!(load_block_entry(&store, &hash_a).unwrap(), Some(block_entry)); + assert_eq!(load_candidate_entry(&store, &candidate_hash).unwrap(), Some(candidate_entry)); + + let delete_keys = vec![ + STORED_BLOCKS_KEY.to_vec(), + blocks_at_height_key(1).to_vec(), + block_entry_key(&hash_a).to_vec(), + candidate_entry_key(&candidate_hash).to_vec(), + ]; + + let delete_keys: Vec<_> = delete_keys.iter().map(|k| &k[..]).collect(); + store.insert_aux(&[], &delete_keys); + + assert!(load_stored_blocks(&store).unwrap().is_none()); + assert!(load_blocks_at_height(&store, 1).unwrap().is_empty()); + assert!(load_block_entry(&store, &hash_a).unwrap().is_none()); + assert!(load_candidate_entry(&store, &candidate_hash).unwrap().is_none()); +} diff --git a/node/primitives/src/approval.rs b/node/primitives/src/approval.rs index 32b4e5af7051..2783cb660202 100644 --- a/node/primitives/src/approval.rs +++ b/node/primitives/src/approval.rs @@ -35,7 +35,7 @@ pub const RELAY_VRF_DELAY_CONTEXT: &str = "A&V TRANCHE"; /// random bytes derived from the VRF submitted within the block by the /// block author as a credential and used as input to approval assignment criteria. -#[derive(Debug, Clone, Encode, Decode)] +#[derive(Debug, Clone, Encode, Decode, PartialEq)] pub struct RelayVRF(pub [u8; 32]); /// Different kinds of input data or criteria that can prove a validator's assignment From 0d384f7fa59f82266e092d78c1b20f3b3629a1f9 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 18:03:08 -0500 Subject: [PATCH 27/35] block_entry test function --- .../approval-voting/src/aux_schema/tests.rs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index db5ce7e53e45..e49e94dd34e8 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -77,6 +77,25 @@ impl TestStore { } } +fn make_bitvec(len: usize) -> BitVec { + bitvec::bitvec![BitOrderLsb0, u8; 0; len] +} + +fn make_block_entry( + block_hash: Hash, + candidates: Vec<(CoreIndex, CandidateHash)>, +) -> BlockEntry { + BlockEntry { + block_hash, + session: 1, + slot: 1, + relay_vrf_story: RelayVRF([0u8; 32]), + approved_bitfield: make_bitvec(candidates.len()), + candidates, + children: Vec::new(), + } +} + #[test] fn read_write() { let store = TestStore::default(); @@ -88,15 +107,10 @@ fn read_write() { let range = StoredBlockRange(10, 20); let at_height = vec![hash_a, hash_b]; - let block_entry = BlockEntry { - block_hash: hash_a, - session: 5, - slot: 10, - relay_vrf_story: RelayVRF([0; 32]), - candidates: vec![(CoreIndex(0), candidate_hash)], - approved_bitfield: Default::default(), - children: Vec::new(), - }; + let block_entry = make_block_entry( + hash_a, + vec![(CoreIndex(0), candidate_hash)], + ); let candidate_entry = CandidateEntry { candidate: Default::default(), From b07f7a98d5586674400e01c89f3496e533874c70 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 18:19:18 -0500 Subject: [PATCH 28/35] add a test for `add_block_entry` --- .../approval-voting/src/aux_schema/mod.rs | 1 + .../approval-voting/src/aux_schema/tests.rs | 75 +++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index beb20289aa6b..4194b18ceb05 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -348,6 +348,7 @@ fn load_decode(store: &impl AuxStore, key: &[u8]) /// Information about a new candidate necessary to instantiate the requisite /// candidate and approval entries. +#[derive(Clone)] pub(crate) struct NewCandidateInfo { candidate: CandidateReceipt, backing_group: GroupIndex, diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index e49e94dd34e8..b5ffd2d8c0b0 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -18,6 +18,7 @@ use super::*; use std::cell::RefCell; +use polkadot_primitives::v1::Id as ParaId; #[derive(Default)] struct TestStore { @@ -96,6 +97,15 @@ fn make_block_entry( } } +fn make_candidate(para_id: ParaId, relay_parent: Hash) -> CandidateReceipt { + let mut c = CandidateReceipt::default(); + + c.descriptor.para_id = para_id; + c.descriptor.relay_parent = relay_parent; + + c +} + #[test] fn read_write() { let store = TestStore::default(); @@ -153,3 +163,68 @@ fn read_write() { assert!(load_block_entry(&store, &hash_a).unwrap().is_none()); assert!(load_candidate_entry(&store, &candidate_hash).unwrap().is_none()); } + +#[test] +fn add_block_entry_works() { + let store = TestStore::default(); + + let parent_hash = Hash::repeat_byte(1); + let block_hash_a = Hash::repeat_byte(2); + let block_hash_b = Hash::repeat_byte(69); + + let candidate_hash_a = CandidateHash(Hash::repeat_byte(3)); + let candidate_hash_b = CandidateHash(Hash::repeat_byte(4)); + + let block_entry_a = make_block_entry( + block_hash_a, + vec![(CoreIndex(0), candidate_hash_a)], + ); + + let block_entry_b = make_block_entry( + block_hash_b, + vec![(CoreIndex(0), candidate_hash_a), (CoreIndex(1), candidate_hash_b)], + ); + + let n_validators = 10; + let block_number = 10; + + let mut new_candidate_info = HashMap::new(); + new_candidate_info.insert(candidate_hash_a, NewCandidateInfo { + candidate: make_candidate(1.into(), parent_hash), + backing_group: GroupIndex(0), + our_assignment: None, + }); + + add_block_entry( + &store, + parent_hash, + block_number, + block_entry_a.clone(), + n_validators, + |h| new_candidate_info.get(h).map(|x| x.clone()), + ).unwrap(); + + new_candidate_info.insert(candidate_hash_b, NewCandidateInfo { + candidate: make_candidate(2.into(), parent_hash), + backing_group: GroupIndex(1), + our_assignment: None, + }); + + add_block_entry( + &store, + parent_hash, + block_number, + block_entry_b.clone(), + n_validators, + |h| new_candidate_info.get(h).map(|x| x.clone()), + ).unwrap(); + + assert_eq!(load_block_entry(&store, &block_hash_a).unwrap(), Some(block_entry_a)); + assert_eq!(load_block_entry(&store, &block_hash_b).unwrap(), Some(block_entry_b)); + + let candidate_entry_a = load_candidate_entry(&store, &candidate_hash_a).unwrap().unwrap(); + assert_eq!(candidate_entry_a.block_assignments.keys().collect::>(), vec![&block_hash_a, &block_hash_b]); + + let candidate_entry_b = load_candidate_entry(&store, &candidate_hash_b).unwrap().unwrap(); + assert_eq!(candidate_entry_b.block_assignments.keys().collect::>(), vec![&block_hash_b]); +} From ec798fb7c2729c0f8e6966c4410ff59254826436 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 18:34:26 -0500 Subject: [PATCH 29/35] ensure range is exclusive at end --- node/core/approval-voting/src/aux_schema/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index 4194b18ceb05..45e065b74546 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -266,7 +266,7 @@ pub(crate) fn canonicalize( // if a relatively short fork is canonicalized. let new_range = StoredBlockRange( canon_number + 1, - std::cmp::max(range.1, canon_number + 1), + std::cmp::max(range.1, canon_number + 2), ).encode(); // Because aux-store requires &&[u8], we have to collect. From 10613b454e15694efcc2e69d4e443eee51401c6a Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 18:34:35 -0500 Subject: [PATCH 30/35] test clear() --- .../approval-voting/src/aux_schema/mod.rs | 1 + .../approval-voting/src/aux_schema/tests.rs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index 45e065b74546..69db7cc8a33b 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -326,6 +326,7 @@ pub(crate) fn clear(store: &impl AuxStore) let visited_keys_borrowed = visited_height_keys.iter().map(|x| &x[..]) .chain(visited_block_keys.iter().map(|x| &x[..])) .chain(visited_candidate_keys.iter().map(|x| &x[..])) + .chain(std::iter::once(&STORED_BLOCKS_KEY[..])) .collect::>(); store.insert_aux(&[], &visited_keys_borrowed)?; diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index b5ffd2d8c0b0..564445b76817 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -228,3 +228,53 @@ fn add_block_entry_works() { let candidate_entry_b = load_candidate_entry(&store, &candidate_hash_b).unwrap().unwrap(); assert_eq!(candidate_entry_b.block_assignments.keys().collect::>(), vec![&block_hash_b]); } + +#[test] +fn clear_works() { + let store = TestStore::default(); + + let hash_a = Hash::repeat_byte(1); + let hash_b = Hash::repeat_byte(2); + let candidate_hash = CandidateHash(Hash::repeat_byte(3)); + + let range = StoredBlockRange(0, 5); + let at_height = vec![hash_a, hash_b]; + + let block_entry = make_block_entry( + hash_a, + vec![(CoreIndex(0), candidate_hash)], + ); + + let candidate_entry = CandidateEntry { + candidate: Default::default(), + session: 5, + block_assignments: vec![ + (hash_a, ApprovalEntry { + tranches: Vec::new(), + backing_group: GroupIndex(1), + next_wakeup: 1000, + our_assignment: None, + assignments: Default::default(), + approved: false, + }) + ].into_iter().collect(), + approvals: Default::default(), + }; + + store.write_stored_blocks(range.clone()); + store.write_blocks_at_height(1, &at_height); + store.write_block_entry(&hash_a, &block_entry); + store.write_candidate_entry(&candidate_hash, &candidate_entry); + + assert_eq!(load_stored_blocks(&store).unwrap(), Some(range)); + assert_eq!(load_blocks_at_height(&store, 1).unwrap(), at_height); + assert_eq!(load_block_entry(&store, &hash_a).unwrap(), Some(block_entry)); + assert_eq!(load_candidate_entry(&store, &candidate_hash).unwrap(), Some(candidate_entry)); + + clear(&store).unwrap(); + + assert!(load_stored_blocks(&store).unwrap().is_none()); + assert!(load_blocks_at_height(&store, 1).unwrap().is_empty()); + assert!(load_block_entry(&store, &hash_a).unwrap().is_none()); + assert!(load_candidate_entry(&store, &candidate_hash).unwrap().is_none()); +} From bbe4b0c36e1a1ff2f729d9547e71707147fcae83 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 18:40:37 -0500 Subject: [PATCH 31/35] test that add_block sets children --- .../approval-voting/src/aux_schema/tests.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index 564445b76817..eea459593332 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -229,6 +229,50 @@ fn add_block_entry_works() { assert_eq!(candidate_entry_b.block_assignments.keys().collect::>(), vec![&block_hash_b]); } +#[test] +fn add_block_entry_adds_child() { + let store = TestStore::default(); + + let parent_hash = Hash::repeat_byte(1); + let block_hash_a = Hash::repeat_byte(2); + let block_hash_b = Hash::repeat_byte(69); + + let mut block_entry_a = make_block_entry( + block_hash_a, + Vec::new(), + ); + + let block_entry_b = make_block_entry( + block_hash_b, + Vec::new(), + ); + + let n_validators = 10; + + add_block_entry( + &store, + parent_hash, + 1, + block_entry_a.clone(), + n_validators, + |_| None, + ).unwrap(); + + add_block_entry( + &store, + block_hash_a, + 2, + block_entry_b.clone(), + n_validators, + |_| None, + ).unwrap(); + + block_entry_a.children.push(block_hash_b); + + assert_eq!(load_block_entry(&store, &block_hash_a).unwrap(), Some(block_entry_a)); + assert_eq!(load_block_entry(&store, &block_hash_b).unwrap(), Some(block_entry_b)); +} + #[test] fn clear_works() { let store = TestStore::default(); From a13d54c55277cbadacfd35c2cd68f3a9737baa91 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Wed, 23 Dec 2020 19:16:04 -0500 Subject: [PATCH 32/35] add a test for canonicalize --- .../approval-voting/src/aux_schema/tests.rs | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index eea459593332..78d924326982 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -322,3 +322,196 @@ fn clear_works() { assert!(load_block_entry(&store, &hash_a).unwrap().is_none()); assert!(load_candidate_entry(&store, &candidate_hash).unwrap().is_none()); } + + +#[test] +fn canonicalize_works() { + let store = TestStore::default(); + + // -> B1 -> C1 -> D1 + // A -> B2 -> C2 -> D2 + // + // We'll canonicalize C1. Everytning except D1 should disappear. + // + // Candidates: + // Cand1 in B2 + // Cand2 in C2 + // Cand3 in C2 and D1 + // Cand4 in D1 + // Cand5 in D2 + // Only Cand3 and Cand4 should remain after canonicalize. + + let n_validators = 10; + + store.write_stored_blocks(StoredBlockRange(1, 5)); + + let genesis = Hash::repeat_byte(0); + + let block_hash_a = Hash::repeat_byte(1); + let block_hash_b1 = Hash::repeat_byte(2); + let block_hash_b2 = Hash::repeat_byte(3); + let block_hash_c1 = Hash::repeat_byte(4); + let block_hash_c2 = Hash::repeat_byte(5); + let block_hash_d1 = Hash::repeat_byte(6); + let block_hash_d2 = Hash::repeat_byte(7); + + let cand_hash_1 = CandidateHash(Hash::repeat_byte(10)); + let cand_hash_2 = CandidateHash(Hash::repeat_byte(11)); + let cand_hash_3 = CandidateHash(Hash::repeat_byte(12)); + let cand_hash_4 = CandidateHash(Hash::repeat_byte(13)); + let cand_hash_5 = CandidateHash(Hash::repeat_byte(15)); + + let block_entry_a = make_block_entry(block_hash_a, Vec::new()); + let block_entry_b1 = make_block_entry(block_hash_b1, Vec::new()); + let block_entry_b2 = make_block_entry(block_hash_b2, vec![(CoreIndex(0), cand_hash_1)]); + let block_entry_c1 = make_block_entry(block_hash_c1, Vec::new()); + let block_entry_c2 = make_block_entry( + block_hash_c2, + vec![(CoreIndex(0), cand_hash_2), (CoreIndex(1), cand_hash_3)], + ); + let block_entry_d1 = make_block_entry( + block_hash_d1, + vec![(CoreIndex(0), cand_hash_3), (CoreIndex(1), cand_hash_4)], + ); + let block_entry_d2 = make_block_entry(block_hash_d2, vec![(CoreIndex(0), cand_hash_5)]); + + + let candidate_info = { + let mut candidate_info = HashMap::new(); + candidate_info.insert(cand_hash_1, NewCandidateInfo { + candidate: make_candidate(1.into(), genesis), + backing_group: GroupIndex(1), + our_assignment: None, + }); + + candidate_info.insert(cand_hash_2, NewCandidateInfo { + candidate: make_candidate(2.into(), block_hash_a), + backing_group: GroupIndex(2), + our_assignment: None, + }); + + candidate_info.insert(cand_hash_3, NewCandidateInfo { + candidate: make_candidate(3.into(), block_hash_a), + backing_group: GroupIndex(3), + our_assignment: None, + }); + + candidate_info.insert(cand_hash_4, NewCandidateInfo { + candidate: make_candidate(4.into(), block_hash_b1), + backing_group: GroupIndex(4), + our_assignment: None, + }); + + candidate_info.insert(cand_hash_5, NewCandidateInfo { + candidate: make_candidate(5.into(), block_hash_c1), + backing_group: GroupIndex(5), + our_assignment: None, + }); + + candidate_info + }; + + // now insert all the blocks. + let blocks = vec![ + (genesis, 1, block_entry_a.clone()), + (block_hash_a, 2, block_entry_b1.clone()), + (block_hash_a, 2, block_entry_b2.clone()), + (block_hash_b1, 3, block_entry_c1.clone()), + (block_hash_b2, 3, block_entry_c2.clone()), + (block_hash_c1, 4, block_entry_d1.clone()), + (block_hash_c2, 4, block_entry_d2.clone()), + ]; + + for (parent_hash, number, block_entry) in blocks { + add_block_entry( + &store, + parent_hash, + number, + block_entry, + n_validators, + |h| candidate_info.get(h).map(|x| x.clone()), + ).unwrap(); + } + + let check_candidates_in_store = |expected: Vec<(CandidateHash, Option>)>| { + for (c_hash, in_blocks) in expected { + let (entry, in_blocks) = match in_blocks { + None => { + assert!(load_candidate_entry(&store, &c_hash).unwrap().is_none()); + continue + } + Some(i) => ( + load_candidate_entry(&store, &c_hash).unwrap().unwrap(), + i, + ), + }; + + assert_eq!(entry.block_assignments.len(), in_blocks.len()); + + for x in in_blocks { + assert!(entry.block_assignments.contains_key(&x)); + } + } + }; + + let check_blocks_in_store = |expected: Vec<(Hash, Option>)>| { + for (hash, with_candidates) in expected { + let (entry, with_candidates) = match with_candidates { + None => { + assert!(load_block_entry(&store, &hash).unwrap().is_none()); + continue + } + Some(i) => ( + load_block_entry(&store, &hash).unwrap().unwrap(), + i, + ), + }; + + assert_eq!(entry.candidates.len(), with_candidates.len()); + + for x in with_candidates { + assert!(entry.candidates.iter().position(|&(_, ref c)| c == &x).is_some()); + } + } + }; + + check_candidates_in_store(vec![ + (cand_hash_1, Some(vec![block_hash_b2])), + (cand_hash_2, Some(vec![block_hash_c2])), + (cand_hash_3, Some(vec![block_hash_c2, block_hash_d1])), + (cand_hash_4, Some(vec![block_hash_d1])), + (cand_hash_5, Some(vec![block_hash_d2])), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, Some(vec![])), + (block_hash_b1, Some(vec![])), + (block_hash_b2, Some(vec![cand_hash_1])), + (block_hash_c1, Some(vec![])), + (block_hash_c2, Some(vec![cand_hash_2, cand_hash_3])), + (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_d2, Some(vec![cand_hash_5])), + ]); + + canonicalize(&store, 3, block_hash_c1).unwrap(); + + assert_eq!(load_stored_blocks(&store).unwrap().unwrap(), StoredBlockRange(4, 5)); + + check_candidates_in_store(vec![ + (cand_hash_1, None), + (cand_hash_2, None), + (cand_hash_3, Some(vec![block_hash_d1])), + (cand_hash_4, Some(vec![block_hash_d1])), + (cand_hash_5, None), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, None), + (block_hash_b1, None), + (block_hash_b2, None), + (block_hash_c1, None), + (block_hash_c2, None), + (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_d2, None), + ]); +} From e336e4537becad75d9aee60a992588687fbc1bb1 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 5 Jan 2021 09:23:32 -0500 Subject: [PATCH 33/35] Update node/core/approval-voting/src/aux_schema/mod.rs Co-authored-by: Peter Goodspeed-Niklaus --- node/core/approval-voting/src/aux_schema/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index 69db7cc8a33b..08672b62eba3 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -110,7 +110,7 @@ pub(crate) struct BlockEntry { #[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct StoredBlockRange(BlockNumber, BlockNumber); -// TODO https://github.com/paritytech/polkadot/1975: probably in lib.rs +// TODO https://github.com/paritytech/polkadot/issues/1975: probably in lib.rs #[derive(Debug, Clone, Encode, Decode, PartialEq)] pub(crate) struct OurAssignment { } From 69fb1b4a12f3a15f6a7996a000d24120e663cb73 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 5 Jan 2021 09:24:30 -0500 Subject: [PATCH 34/35] Update node/core/approval-voting/src/aux_schema/tests.rs Co-authored-by: Peter Goodspeed-Niklaus --- node/core/approval-voting/src/aux_schema/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema/tests.rs b/node/core/approval-voting/src/aux_schema/tests.rs index 78d924326982..97fd27d597ef 100644 --- a/node/core/approval-voting/src/aux_schema/tests.rs +++ b/node/core/approval-voting/src/aux_schema/tests.rs @@ -331,7 +331,7 @@ fn canonicalize_works() { // -> B1 -> C1 -> D1 // A -> B2 -> C2 -> D2 // - // We'll canonicalize C1. Everytning except D1 should disappear. + // We'll canonicalize C1. Everything except D1 should disappear. // // Candidates: // Cand1 in B2 From 1e4be6e25f476e24cdb01140faa3f1273521e1ff Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Tue, 5 Jan 2021 13:17:45 -0500 Subject: [PATCH 35/35] Update node/core/approval-voting/src/aux_schema/mod.rs Co-authored-by: Peter Goodspeed-Niklaus --- node/core/approval-voting/src/aux_schema/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/core/approval-voting/src/aux_schema/mod.rs b/node/core/approval-voting/src/aux_schema/mod.rs index 08672b62eba3..fd55644a1918 100644 --- a/node/core/approval-voting/src/aux_schema/mod.rs +++ b/node/core/approval-voting/src/aux_schema/mod.rs @@ -30,7 +30,7 @@ //! In the future, we may use a temporary DB which doesn't need to be wiped, but for the //! time being we share the same DB with the rest of Substrate. -// TODO https://github.com/paritytech/polkadot/1975: remove this +// TODO https://github.com/paritytech/polkadot/issues/1975: remove this #![allow(unused)] use sc_client_api::backend::AuxStore;