Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Refactor election solution trimming for efficiency #8614

Merged
35 commits merged into from
May 3, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
50a24bc
Refactor election solution trimming for efficiency
coriolinus Apr 13, 2021
d3cba7b
rework length-trim tests to work with the new interface
coriolinus Apr 14, 2021
919c98d
simplify
coriolinus Apr 14, 2021
9160387
add a fuzzer which can validate `Compact::encoded_size_for`
coriolinus Apr 14, 2021
b7fe186
Revert "add a fuzzer which can validate `Compact::encoded_size_for`"
coriolinus Apr 14, 2021
47dceb8
revert changes to `trait CompactSolution`
coriolinus Apr 14, 2021
25cd75e
WIP: restructure trim_assignments_length by actually encoding
coriolinus Apr 14, 2021
f9055e2
fix compiler errors
coriolinus Apr 15, 2021
24bf2ad
don't sort voters, just assignments
coriolinus Apr 15, 2021
983cac1
WIP: add `IndexAssignment` type to speed up repeatedly creating `Comp…
coriolinus Apr 15, 2021
e4287c7
Add IndexAssignment and conversion method to CompactSolution
coriolinus Apr 16, 2021
232ec2b
use `CompactSolution::from_index_assignment` and clean up dead code
coriolinus Apr 16, 2021
c209b80
get rid of `from_index_assignments` in favor of `TryFrom`
coriolinus Apr 16, 2021
60ee91e
cause `pallet-election-provider-multi-phase` tests to compile success…
coriolinus Apr 16, 2021
4e42ed4
fix infinite binary search loop
coriolinus Apr 16, 2021
859ebf3
fix a test failure
coriolinus Apr 16, 2021
1a3987b
fix the rest of test failures
coriolinus Apr 16, 2021
9601dd4
remove unguarded subtraction
coriolinus Apr 16, 2021
b04e165
fix npos-elections tests compilation
coriolinus Apr 19, 2021
31222ca
ensure we use sp_std::vec::Vec in assignments
coriolinus Apr 19, 2021
5602e93
Merge remote-tracking branch 'origin/master' into prgn-multi-phase-ef…
coriolinus Apr 19, 2021
3dda173
add IndexAssignmentOf to sp_npos_elections
coriolinus Apr 19, 2021
0c76b22
move miner types to `unsigned`
coriolinus Apr 19, 2021
9960342
use stable sort
coriolinus Apr 19, 2021
859555a
rewrap some long comments
coriolinus Apr 19, 2021
dd80c95
use existing cache instead of building a dedicated stake map
coriolinus Apr 19, 2021
32ed1ec
generalize the TryFrom bound on CompactSolution
coriolinus Apr 19, 2021
0edfca5
undo adding sp-core dependency
coriolinus Apr 19, 2021
e4d2823
consume assignments to produce index_assignments
coriolinus Apr 19, 2021
73d3fdd
Add a test of Assignment -> IndexAssignment -> Compact
coriolinus Apr 19, 2021
1a174aa
fix `IndexAssignmentOf` doc
coriolinus Apr 20, 2021
eed1c21
move compact test from sp-npos-elections-compact to sp-npos-elections
coriolinus Apr 20, 2021
8f0e9c5
rename assignments -> sorted_assignments
coriolinus Apr 21, 2021
26726f8
sort after reducing to avoid potential re-sort issues
coriolinus Apr 21, 2021
c9f2517
add runtime benchmark, fix critical binary search error
coriolinus Apr 21, 2021
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

4 changes: 2 additions & 2 deletions frame/election-provider-multi-phase/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ log = { version = "0.4.14", default-features = false }
frame-support = { version = "3.0.0", default-features = false, path = "../support" }
frame-system = { version = "3.0.0", default-features = false, path = "../system" }

sp-io ={ version = "3.0.0", default-features = false, path = "../../primitives/io" }
sp-io = { version = "3.0.0", default-features = false, path = "../../primitives/io" }
sp-std = { version = "3.0.0", default-features = false, path = "../../primitives/std" }
sp-runtime = { version = "3.0.0", default-features = false, path = "../../primitives/runtime" }
sp-npos-elections = { version = "3.0.0", default-features = false, path = "../../primitives/npos-elections" }
Expand All @@ -37,8 +37,8 @@ parking_lot = "0.11.0"
rand = { version = "0.7.3" }
hex-literal = "0.3.1"
substrate-test-utils = { version = "3.0.0", path = "../../test-utils" }
sp-core = { version = "3.0.0", default-features = false, path = "../../primitives/core" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of curiosity why is it needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, actually; I just put it back into the dev-dependencies where it was before this PR. Looks like I assumed (wrongly) that it had previously been alphabetized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I wanted to ask about the requirement of default-features = false.
For now we always test with std. thus we usually don't specify default-features = false.
(Anyway I expect sp-core std feature to be activated by other crate when running the tests)

sp-io = { version = "3.0.0", path = "../../primitives/io" }
sp-core = { version = "3.0.0", path = "../../primitives/core" }
sp-tracing = { version = "3.0.0", path = "../../primitives/tracing" }
frame-election-provider-support = { version = "3.0.0", features = ["runtime-benchmarks"], path = "../election-provider-support" }
pallet-balances = { version = "3.0.0", path = "../balances" }
Expand Down
2 changes: 1 addition & 1 deletion frame/election-provider-multi-phase/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ fn solution_with_size<T: Config>(
.collect::<Vec<_>>();

let compact =
<CompactOf<T>>::from_assignment(assignments, &voter_index, &target_index).unwrap();
<CompactOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
let score = compact.clone().score(&winners, stake_of, voter_at, target_at).unwrap();
let round = <MultiPhase<T>>::round();

Expand Down
12 changes: 12 additions & 0 deletions frame/election-provider-multi-phase/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ pub fn voter_index_fn<T: Config>(
}
}

/// Create a function that returns the index of a voter in the snapshot.
///
/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
/// borrowed.
pub fn voter_index_fn_owned<T: Config>(
cache: BTreeMap<T::AccountId, usize>,
) -> impl Fn(&T::AccountId) -> Option<CompactVoterIndexOf<T>> {
move |who| {
cache.get(who).and_then(|i| <usize as TryInto<CompactVoterIndexOf<T>>>::try_into(*i).ok())
}
}

/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible.
///
/// ## Warning
Expand Down
74 changes: 66 additions & 8 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use super::*;
use crate as multi_phase;
use multi_phase::unsigned::{IndexAssignmentOf, Voter};
pub use frame_support::{assert_noop, assert_ok};
use frame_support::{
parameter_types,
Expand All @@ -41,7 +42,7 @@ use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
PerU16,
};
use std::sync::Arc;
use std::{convert::TryFrom, sync::Arc};

pub type Block = sp_runtime::generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic<AccountId, Call, (), ()>;
Expand Down Expand Up @@ -95,19 +96,70 @@ pub fn roll_to_with_ocw(n: u64) {
}
}

pub struct TrimHelpers {
pub voters: Vec<Voter<Runtime>>,
pub assignments: Vec<IndexAssignmentOf<Runtime>>,
pub encoded_size_of:
Box<dyn Fn(&[IndexAssignmentOf<Runtime>]) -> Result<usize, sp_npos_elections::Error>>,
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
pub voter_index: Box<
dyn Fn(
&<Runtime as frame_system::Config>::AccountId,
) -> Option<CompactVoterIndexOf<Runtime>>,
>,
}

/// Helpers for setting up trimming tests.
///
/// Assignments are pre-sorted in reverse order of stake.
pub fn trim_helpers() -> TrimHelpers {
let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap();
let stakes: std::collections::HashMap<_, _> =
voters.iter().map(|(id, stake, _)| (*id, *stake)).collect();

// Compute the size of a compact solution comprised of the selected arguments.
//
// This function completes in `O(edges)`; it's expensive, but linear.
let encoded_size_of = Box::new(|assignments: &[IndexAssignmentOf<Runtime>]| {
CompactOf::<Runtime>::try_from(assignments).map(|compact| compact.encoded_size())
});
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_owned::<Runtime>(cache);
let target_index = helpers::target_index_fn::<Runtime>(&targets);

let desired_targets = MultiPhase::desired_targets().unwrap();

let ElectionResult { mut assignments, .. } = seq_phragmen::<_, CompactAccuracyOf<Runtime>>(
desired_targets as usize,
targets.clone(),
voters.clone(),
None,
)
.unwrap();

// sort by decreasing order of stake
assignments.sort_unstable_by_key(|assignment| {
std::cmp::Reverse(stakes.get(&assignment.who).cloned().unwrap_or_default())
});

// convert to IndexAssignment
let assignments = assignments
.iter()
.map(|assignment| {
IndexAssignmentOf::<Runtime>::new(assignment, &voter_index, &target_index)
})
.collect::<Result<Vec<_>, _>>()
.expect("test assignments don't contain any voters with too many votes");

TrimHelpers { voters, assignments, encoded_size_of, voter_index: Box::new(voter_index) }
}

/// Spit out a verifiable raw solution.
///
/// This is a good example of what an offchain miner would do.
pub fn raw_solution() -> RawSolution<CompactOf<Runtime>> {
let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap();
let desired_targets = MultiPhase::desired_targets().unwrap();

// closures
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_linear::<Runtime>(&voters);
let target_index = helpers::target_index_fn_linear::<Runtime>(&targets);
let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache);

let ElectionResult { winners, assignments } = seq_phragmen::<_, CompactAccuracyOf<Runtime>>(
desired_targets as usize,
targets.clone(),
Expand All @@ -116,14 +168,20 @@ pub fn raw_solution() -> RawSolution<CompactOf<Runtime>> {
)
.unwrap();

// closures
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_linear::<Runtime>(&voters);
let target_index = helpers::target_index_fn_linear::<Runtime>(&targets);
let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache);

let winners = to_without_backing(winners);

let score = {
let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap();
to_supports(&winners, &staked).unwrap().evaluate()
};
let compact =
<CompactOf<Runtime>>::from_assignment(assignments, &voter_index, &target_index).unwrap();
<CompactOf<Runtime>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();

let round = MultiPhase::round();
RawSolution { compact, score, round }
Expand Down
Loading