Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 31 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ members = [
"frame/try-runtime",
"frame/election-provider-multi-phase",
"frame/election-provider-support",
"frame/election-provider-support/solution-type",
"frame/election-provider-support/solution-type/fuzzer",
"frame/examples/basic",
"frame/examples/offchain-worker",
"frame/examples/parallel",
Expand Down Expand Up @@ -169,7 +171,6 @@ members = [
"primitives/keystore",
"primitives/maybe-compressed-blob",
"primitives/npos-elections",
"primitives/npos-elections/solution-type",
"primitives/npos-elections/fuzzer",
"primitives/offchain",
"primitives/panic-handler",
Expand Down
2 changes: 1 addition & 1 deletion bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,7 +588,7 @@ parameter_types! {
.get(DispatchClass::Normal);
}

sp_npos_elections::generate_solution_type!(
frame_election_provider_support::generate_solution_type!(
#[compact]
pub struct NposSolution16::<
VoterIndex = u32,
Expand Down
3 changes: 1 addition & 2 deletions frame/election-provider-multi-phase/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive
sp-io = { version = "6.0.0", path = "../../primitives/io" }
sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" }
sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" }
frame-election-provider-support = { version = "4.0.0-dev", features = [
], path = "../election-provider-support" }
frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" }
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" }

Expand Down
2 changes: 1 addition & 1 deletion frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub(crate) type BlockNumber = u64;
pub(crate) type VoterIndex = u32;
pub(crate) type TargetIndex = u16;

sp_npos_elections::generate_solution_type!(
frame_election_provider_support::generate_solution_type!(
#[compact]
pub struct TestNposSolution::<VoterIndex = VoterIndex, TargetIndex = TargetIndex, Accuracy = PerU16>(16)
);
Expand Down
1 change: 1 addition & 0 deletions frame/election-provider-support/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = ".
sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" }

[dev-dependencies]
sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "sp-npos-elections-solution-type"
name = "frame-election-provider-solution-type"
version = "4.0.0-dev"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
Expand All @@ -23,7 +23,7 @@ proc-macro-crate = "1.1.0"
[dev-dependencies]
parity-scale-codec = "3.0.0"
scale-info = "2.0.1"
sp-arithmetic = { path = "../../arithmetic", version = "5.0.0"}
sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" }
# used by generate_solution_type:
sp-npos-elections = { path = "..", version = "4.0.0-dev" }
sp-npos-elections = { version = "4.0.0-dev", path = "../../../primitives/npos-elections" }
trybuild = "1.0.53"
30 changes: 30 additions & 0 deletions frame/election-provider-support/solution-type/fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[package]
name = "frame-election-solution-type-fuzzer"
version = "2.0.0-alpha.5"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "Fuzzer for phragmén solution type implementation."
publish = false

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
clap = { version = "3.0", features = ["derive"] }
honggfuzz = "0.5"
rand = { version = "0.8", features = ["std", "small_rng"] }

codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." }
sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" }
sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" }
# used by generate_solution_type:
sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/npos-elections" }

[[bin]]
name = "compact"
path = "src/compact.rs"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use frame_election_provider_solution_type::generate_solution_type;
use honggfuzz::fuzz;
use sp_npos_elections::{generate_solution_type, sp_arithmetic::Percent};
use sp_arithmetic::Percent;
use sp_runtime::codec::{Encode, Error};

fn main() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// type, `u8` target type and `Perbill` accuracy with maximum of 4 edges per voter.
///
/// ```
/// # use sp_npos_elections_solution_type::generate_solution_type;
/// # use frame_election_provider_solution_type::generate_solution_type;
/// # use sp_arithmetic::per_things::Perbill;
/// generate_solution_type!(pub struct TestSolution::<
/// VoterIndex = u16,
Expand Down Expand Up @@ -100,7 +100,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
/// for numbers will be used, similar to how `parity-scale-codec`'s `Compact` works.
///
/// ```
/// # use sp_npos_elections_solution_type::generate_solution_type;
/// # use frame_election_provider_solution_type::generate_solution_type;
/// # use sp_npos_elections::NposSolution;
/// # use sp_arithmetic::per_things::Perbill;
/// generate_solution_type!(
Expand Down
178 changes: 178 additions & 0 deletions frame/election-provider-support/solution-type/src/mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// This file is part of Substrate.

// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Mock file for solution-type.

#![cfg(test)]

use std::{collections::HashMap, convert::TryInto, hash::Hash, HashSet};

use rand::{seq::SliceRandom, Rng};

/// The candidate mask allows easy disambiguation between voters and candidates: accounts
/// for which this bit is set are candidates, and without it, are voters.
pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::<AccountId>() * 8) - 1);

pub type TestAccuracy = sp_runtime::Perbill;

pub fn p(p: u8) -> TestAccuracy {
TestAccuracy::from_percent(p.into())
}

pub type MockAssignment = crate::Assignment<AccountId, TestAccuracy>;
pub type Voter = (AccountId, VoteWeight, Vec<AccountId>);

crate::generate_solution_type! {
pub struct TestSolution::<
VoterIndex = u32,
TargetIndex = u16,
Accuracy = TestAccuracy,
>(16)
}

/// Generate voter and assignment lists. Makes no attempt to be realistic about winner or assignment
/// fairness.
///
/// Maintains these invariants:
///
/// - candidate ids have `CANDIDATE_MASK` bit set
/// - voter ids do not have `CANDIDATE_MASK` bit set
/// - assignments have the same ordering as voters
/// - `assignments.distribution.iter().map(|(_, frac)| frac).sum() == One::one()`
/// - a coherent set of winners is chosen.
/// - the winner set is a subset of the candidate set.
/// - `assignments.distribution.iter().all(|(who, _)| winners.contains(who))`
pub fn generate_random_votes(
candidate_count: usize,
voter_count: usize,
mut rng: impl Rng,
) -> (Vec<Voter>, Vec<MockAssignment>, Vec<AccountId>) {
// cache for fast generation of unique candidate and voter ids
let mut used_ids = HashSet::with_capacity(candidate_count + voter_count);

// candidates are easy: just a completely random set of IDs
let mut candidates: Vec<AccountId> = Vec::with_capacity(candidate_count);
while candidates.len() < candidate_count {
let mut new = || rng.gen::<AccountId>() | CANDIDATE_MASK;
let mut id = new();
// insert returns `false` when the value was already present
while !used_ids.insert(id) {
id = new();
}
candidates.push(id);
}

// voters are random ids, random weights, random selection from the candidates
let mut voters = Vec::with_capacity(voter_count);
while voters.len() < voter_count {
let mut new = || rng.gen::<AccountId>() & !CANDIDATE_MASK;
let mut id = new();
// insert returns `false` when the value was already present
while !used_ids.insert(id) {
id = new();
}

let vote_weight = rng.gen();

// it's not interesting if a voter chooses 0 or all candidates, so rule those cases out.
// also, let's not generate any cases which result in a compact overflow.
let n_candidates_chosen =
rng.gen_range(1, candidates.len().min(<TestSolution as crate::NposSolution>::LIMIT));

let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen);
chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen));
voters.push((id, vote_weight, chosen_candidates));
}

// always generate a sensible number of winners: elections are uninteresting if nobody wins,
// or everybody wins
let num_winners = rng.gen_range(1, candidate_count);
let mut winners: HashSet<AccountId> = HashSet::with_capacity(num_winners);
winners.extend(candidates.choose_multiple(&mut rng, num_winners));
assert_eq!(winners.len(), num_winners);

let mut assignments = Vec::with_capacity(voters.len());
for (voter_id, _, votes) in voters.iter() {
let chosen_winners = votes.iter().filter(|vote| winners.contains(vote)).cloned();
let num_chosen_winners = chosen_winners.clone().count();

// distribute the available stake randomly
let stake_distribution = if num_chosen_winners == 0 {
continue
} else {
let mut available_stake = 1000;
let mut stake_distribution = Vec::with_capacity(num_chosen_winners);
for _ in 0..num_chosen_winners - 1 {
let stake = rng.gen_range(0, available_stake).min(1);
stake_distribution.push(TestAccuracy::from_perthousand(stake));
available_stake -= stake;
}
stake_distribution.push(TestAccuracy::from_perthousand(available_stake));
stake_distribution.shuffle(&mut rng);
stake_distribution
};

assignments.push(MockAssignment {
who: *voter_id,
distribution: chosen_winners.zip(stake_distribution).collect(),
});
}

(voters, assignments, candidates)
}

fn generate_cache<Voters, Item>(voters: Voters) -> HashMap<Item, usize>
where
Voters: Iterator<Item = Item>,
Item: Hash + Eq + Copy,
{
let mut cache = HashMap::new();
for (idx, voter_id) in voters.enumerate() {
cache.insert(voter_id, idx);
}
cache
}

/// Create a function that returns the index of a voter in the voters list.
pub fn make_voter_fn<VoterIndex>(voters: &[Voter]) -> impl Fn(&AccountId) -> Option<VoterIndex>
where
usize: TryInto<VoterIndex>,
{
let cache = generate_cache(voters.iter().map(|(id, _, _)| *id));
move |who| {
if cache.get(who).is_none() {
println!("WARNING: voter {} will raise InvalidIndex", who);
}
cache.get(who).cloned().and_then(|i| i.try_into().ok())
}
}

/// Create a function that returns the index of a candidate in the candidates list.
pub fn make_target_fn<TargetIndex>(
candidates: &[AccountId],
) -> impl Fn(&AccountId) -> Option<TargetIndex>
where
usize: TryInto<TargetIndex>,
{
let cache = generate_cache(candidates.iter().cloned());
move |who| {
if cache.get(who).is_none() {
println!("WARNING: target {} will raise InvalidIndex", who);
}
cache.get(who).cloned().and_then(|i| i.try_into().ok())
}
}
Loading