Decouple Staking and Election - Part 2 Unsigned Phase (#7909)

* Base features and traits.

* pallet and unsigned phase

* Undo bad formattings.

* some formatting cleanup.

* Small self-cleanup.

* Make it all build

* self-review

* Some doc tests.

* Some changes from other PR

* Fix session test

* Update Cargo.lock

* Update frame/election-provider-multi-phase/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Some review comments

* Rename + make encode/decode

* Do an assert as well, just in case.

* Fix build

* Update frame/election-provider-multi-phase/src/unsigned.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Las comment

* fix staking fuzzer.

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Add one last layer of feasibility check as well.

* Last fixes to benchmarks

* Some more docs.

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_election_provider_multi_phase --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/election-provider-multi-phase/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Some nits

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_staking --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/staking/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Fix doc

* Mkae ci green

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
Kian Paimani
2021-02-23 14:46:17 +00:00
committed by GitHub
parent ba659f9440
commit 7205eea40d
34 changed files with 4092 additions and 231 deletions
@@ -0,0 +1,282 @@
// This file is part of Substrate.
// Copyright (C) 2020 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.
//! Two phase election pallet benchmarking.
use super::*;
use crate::Module as MultiPhase;
pub use frame_benchmarking::{account, benchmarks, whitelist_account, whitelisted_caller};
use frame_support::{assert_ok, traits::OnInitialize};
use frame_system::RawOrigin;
use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
use sp_election_providers::Assignment;
use sp_arithmetic::traits::One;
use sp_runtime::InnerOf;
use sp_std::convert::TryInto;
const SEED: u32 = 0;
/// Creates a **valid** solution with exactly the given size.
///
/// The snapshot is also created internally.
fn solution_with_size<T: Config>(
size: SolutionOrSnapshotSize,
active_voters_count: u32,
desired_targets: u32,
) -> RawSolution<CompactOf<T>> {
assert!(size.targets >= desired_targets, "must have enough targets");
assert!(
size.targets >= (<CompactOf<T>>::LIMIT * 2) as u32,
"must have enough targets for unique votes."
);
assert!(size.voters >= active_voters_count, "must have enough voters");
assert!(
(<CompactOf<T>>::LIMIT as u32) < desired_targets,
"must have enough winners to give them votes."
);
let ed: VoteWeight = T::Currency::minimum_balance().saturated_into::<u64>();
let stake: VoteWeight = ed.max(One::one()).saturating_mul(100);
// first generates random targets.
let targets: Vec<T::AccountId> =
(0..size.targets).map(|i| account("Targets", i, SEED)).collect();
let mut rng = SmallRng::seed_from_u64(999u64);
// decide who are the winners.
let winners = targets
.as_slice()
.choose_multiple(&mut rng, desired_targets as usize)
.cloned()
.collect::<Vec<_>>();
// first generate active voters who must vote for a subset of winners.
let active_voters = (0..active_voters_count)
.map(|i| {
// chose a random subset of winners.
let winner_votes = winners
.as_slice()
.choose_multiple(&mut rng, <CompactOf<T>>::LIMIT)
.cloned()
.collect::<Vec<_>>();
let voter = account::<T::AccountId>("Voter", i, SEED);
(voter, stake, winner_votes)
})
.collect::<Vec<_>>();
// rest of the voters. They can only vote for non-winners.
let non_winners =
targets.iter().filter(|t| !winners.contains(t)).cloned().collect::<Vec<T::AccountId>>();
let rest_voters = (active_voters_count..size.voters)
.map(|i| {
let votes = (&non_winners)
.choose_multiple(&mut rng, <CompactOf<T>>::LIMIT)
.cloned()
.collect::<Vec<T::AccountId>>();
let voter = account::<T::AccountId>("Voter", i, SEED);
(voter, stake, votes)
})
.collect::<Vec<_>>();
let mut all_voters = active_voters.clone();
all_voters.extend(rest_voters);
all_voters.shuffle(&mut rng);
assert_eq!(active_voters.len() as u32, active_voters_count);
assert_eq!(all_voters.len() as u32, size.voters);
assert_eq!(winners.len() as u32, desired_targets);
<SnapshotMetadata<T>>::put(SolutionOrSnapshotSize {
voters: all_voters.len() as u32,
targets: targets.len() as u32,
});
<DesiredTargets<T>>::put(desired_targets);
<Snapshot<T>>::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() });
// write the snapshot to staking or whoever is the data provider.
T::DataProvider::put_snapshot(all_voters.clone(), targets.clone());
let cache = helpers::generate_voter_cache::<T>(&all_voters);
let stake_of = helpers::stake_of_fn::<T>(&all_voters, &cache);
let voter_index = helpers::voter_index_fn::<T>(&cache);
let target_index = helpers::target_index_fn_linear::<T>(&targets);
let voter_at = helpers::voter_at_fn::<T>(&all_voters);
let target_at = helpers::target_at_fn::<T>(&targets);
let assignments = active_voters
.iter()
.map(|(voter, _stake, votes)| {
let percent_per_edge: InnerOf<CompactAccuracyOf<T>> =
(100 / votes.len()).try_into().unwrap_or_else(|_| panic!("failed to convert"));
Assignment {
who: voter.clone(),
distribution: votes
.iter()
.map(|t| (t.clone(), <CompactAccuracyOf<T>>::from_percent(percent_per_edge)))
.collect::<Vec<_>>(),
}
})
.collect::<Vec<_>>();
let compact =
<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();
RawSolution { compact, score, round }
}
benchmarks! {
on_initialize_nothing {
assert!(<MultiPhase<T>>::current_phase().is_off());
}: {
<MultiPhase<T>>::on_initialize(1u32.into());
} verify {
assert!(<MultiPhase<T>>::current_phase().is_off());
}
on_initialize_open_signed {
// NOTE: this benchmark currently doesn't have any components because the length of a db
// read/write is not captured. Otherwise, it is quite influenced by how much data
// `T::ElectionDataProvider` is reading and passing on.
assert!(<MultiPhase<T>>::snapshot().is_none());
assert!(<MultiPhase<T>>::current_phase().is_off());
}: {
<MultiPhase<T>>::on_initialize_open_signed();
} verify {
assert!(<MultiPhase<T>>::snapshot().is_some());
assert!(<MultiPhase<T>>::current_phase().is_signed());
}
on_initialize_open_unsigned_with_snapshot {
assert!(<MultiPhase<T>>::snapshot().is_none());
assert!(<MultiPhase<T>>::current_phase().is_off());
}: {
<MultiPhase<T>>::on_initialize_open_unsigned(true, true, 1u32.into());
} verify {
assert!(<MultiPhase<T>>::snapshot().is_some());
assert!(<MultiPhase<T>>::current_phase().is_unsigned());
}
on_initialize_open_unsigned_without_snapshot {
// need to assume signed phase was open before
<MultiPhase<T>>::on_initialize_open_signed();
assert!(<MultiPhase<T>>::snapshot().is_some());
assert!(<MultiPhase<T>>::current_phase().is_signed());
}: {
<MultiPhase<T>>::on_initialize_open_unsigned(false, true, 1u32.into());
} verify {
assert!(<MultiPhase<T>>::snapshot().is_some());
assert!(<MultiPhase<T>>::current_phase().is_unsigned());
}
#[extra]
create_snapshot {
assert!(<MultiPhase<T>>::snapshot().is_none());
}: {
<MultiPhase::<T>>::create_snapshot()
} verify {
assert!(<MultiPhase<T>>::snapshot().is_some());
}
submit_unsigned {
// number of votes in snapshot.
let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1];
// number of targets in snapshot.
let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1];
// number of assignments, i.e. compact.len(). This means the active nominators, thus must be
// a subset of `v` component.
let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1];
// number of desired targets. Must be a subset of `t` component.
let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1];
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
let raw_solution = solution_with_size::<T>(witness, a, d);
assert!(<MultiPhase<T>>::queued_solution().is_none());
<CurrentPhase<T>>::put(Phase::Unsigned((true, 1u32.into())));
// encode the most significant storage item that needs to be decoded in the dispatch.
let encoded_snapshot = <MultiPhase<T>>::snapshot().unwrap().encode();
let encoded_call = <Call<T>>::submit_unsigned(raw_solution.clone(), witness).encode();
}: {
assert_ok!(<MultiPhase<T>>::submit_unsigned(RawOrigin::None.into(), raw_solution, witness));
let _decoded_snap = <RoundSnapshot<T::AccountId> as Decode>::decode(&mut &*encoded_snapshot).unwrap();
let _decoded_call = <Call<T> as Decode>::decode(&mut &*encoded_call).unwrap();
} verify {
assert!(<MultiPhase<T>>::queued_solution().is_some());
}
// This is checking a valid solution. The worse case is indeed a valid solution.
feasibility_check {
// number of votes in snapshot.
let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1];
// number of targets in snapshot.
let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1];
// number of assignments, i.e. compact.len(). This means the active nominators, thus must be
// a subset of `v` component.
let a in (T::BenchmarkingConfig::ACTIVE_VOTERS[0]) .. T::BenchmarkingConfig::ACTIVE_VOTERS[1];
// number of desired targets. Must be a subset of `t` component.
let d in (T::BenchmarkingConfig::DESIRED_TARGETS[0]) .. T::BenchmarkingConfig::DESIRED_TARGETS[1];
let size = SolutionOrSnapshotSize { voters: v, targets: t };
let raw_solution = solution_with_size::<T>(size, a, d);
assert_eq!(raw_solution.compact.voter_count() as u32, a);
assert_eq!(raw_solution.compact.unique_targets().len() as u32, d);
// encode the most significant storage item that needs to be decoded in the dispatch.
let encoded_snapshot = <MultiPhase<T>>::snapshot().unwrap().encode();
}: {
assert_ok!(<MultiPhase<T>>::feasibility_check(raw_solution, ElectionCompute::Unsigned));
let _decoded_snap = <RoundSnapshot<T::AccountId> as Decode>::decode(&mut &*encoded_snapshot).unwrap();
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::mock::*;
#[test]
fn test_benchmarks() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_feasibility_check::<Runtime>());
});
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_submit_unsigned::<Runtime>());
});
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_on_initialize_open_unsigned_with_snapshot::<Runtime>());
});
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_on_initialize_open_unsigned_without_snapshot::<Runtime>());
});
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_on_initialize_nothing::<Runtime>());
});
ExtBuilder::default().build_and_execute(|| {
assert_ok!(test_benchmark_create_snapshot::<Runtime>());
});
}
}