mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 03:31:05 +00:00
Implementing MaxEncodedLen for generate_solution_type (#11032)
* Move `sp-npos-elections-solution-type` to `frame-election-provider-support` First stab at it, will need to amend some more stuff * Fixing tests * Fixing tests * Fixing cargo.toml for std configuration * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * fmt * Committing suggested changes renaming, and re exporting macro. * Removing unneeded imports * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * Move `NposSolution` to frame * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * Fixing test * Removing unneeded dependencies * `VoterSnapshotPerBlock` -> `MaxElectingVoters` * rename `SizeBound` to `MaxVoters` * Removing TODO and change bound * renaming `size_bound` to `max_voters` * Enabling tests for `solution-type` These got dropped off after the crate was moved from `sp_npos_elections` * Adding tests for `MaxEncodedLen` of solution_type * Better rustdocs. Better indenting and comments. Removing unneeded imports in tests.
This commit is contained in:
@@ -24,6 +24,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys
|
||||
frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7.3"
|
||||
sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" }
|
||||
sp-core = { version = "6.0.0", path = "../../primitives/core" }
|
||||
sp-io = { version = "6.0.0", path = "../../primitives/io" }
|
||||
|
||||
@@ -26,4 +26,5 @@ scale-info = "2.0.1"
|
||||
sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" }
|
||||
# used by generate_solution_type:
|
||||
frame-election-provider-support = { version = "4.0.0-dev", path = ".." }
|
||||
frame-support = { version = "4.0.0-dev", path = "../../support" }
|
||||
trybuild = "1.0.53"
|
||||
|
||||
@@ -25,6 +25,7 @@ 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" }
|
||||
frame-support = { version = "4.0.0-dev", path = "../../../support" }
|
||||
|
||||
[[bin]]
|
||||
name = "compact"
|
||||
|
||||
@@ -8,6 +8,7 @@ fn main() {
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = Percent,
|
||||
MaxVoters = frame_support::traits::ConstU32::<100_000>,
|
||||
>(16));
|
||||
loop {
|
||||
fuzz!(|fuzzer_data: &[u8]| {
|
||||
|
||||
@@ -49,6 +49,12 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
|
||||
/// compact encoding.
|
||||
/// - The accuracy of the ratios. This must be one of the `PerThing` types defined in
|
||||
/// `sp-arithmetic`.
|
||||
/// - The maximum number of voters. This must be of type `Get<u32>`. Check <https://github.com/paritytech/substrate/issues/10866>
|
||||
/// for more details. This is used to bound the struct, by leveraging the fact that `votes1.len()
|
||||
/// < votes2.len() < ... < votesn.len()` (the details of the struct is explained further below).
|
||||
/// We know that `sum_i votes_i.len() <= MaxVoters`, and we know that the maximum size of the
|
||||
/// struct would be achieved if all voters fall in the last bucket. One can also check the tests
|
||||
/// and more specifically `max_encoded_len_exact` for a concrete example.
|
||||
///
|
||||
/// Moreover, the maximum number of edges per voter (distribution per assignment) also need to be
|
||||
/// specified. Attempting to convert from/to an assignment with more distributions will fail.
|
||||
@@ -59,10 +65,12 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
|
||||
/// ```
|
||||
/// # use frame_election_provider_solution_type::generate_solution_type;
|
||||
/// # use sp_arithmetic::per_things::Perbill;
|
||||
/// # use frame_support::traits::ConstU32;
|
||||
/// generate_solution_type!(pub struct TestSolution::<
|
||||
/// VoterIndex = u16,
|
||||
/// TargetIndex = u8,
|
||||
/// Accuracy = Perbill,
|
||||
/// MaxVoters = ConstU32::<10>,
|
||||
/// >(4));
|
||||
/// ```
|
||||
///
|
||||
@@ -103,9 +111,15 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error {
|
||||
/// # use frame_election_provider_solution_type::generate_solution_type;
|
||||
/// # use frame_election_provider_support::NposSolution;
|
||||
/// # use sp_arithmetic::per_things::Perbill;
|
||||
/// # use frame_support::traits::ConstU32;
|
||||
/// generate_solution_type!(
|
||||
/// #[compact]
|
||||
/// pub struct TestSolutionCompact::<VoterIndex = u16, TargetIndex = u8, Accuracy = Perbill>(8)
|
||||
/// pub struct TestSolutionCompact::<
|
||||
/// VoterIndex = u16,
|
||||
/// TargetIndex = u8,
|
||||
/// Accuracy = Perbill,
|
||||
/// MaxVoters = ConstU32::<10>,
|
||||
/// >(8)
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
@@ -129,6 +143,7 @@ struct SolutionDef {
|
||||
voter_type: syn::Type,
|
||||
target_type: syn::Type,
|
||||
weight_type: syn::Type,
|
||||
max_voters: syn::Type,
|
||||
count: usize,
|
||||
compact_encoding: bool,
|
||||
}
|
||||
@@ -167,11 +182,11 @@ impl Parse for SolutionDef {
|
||||
let _ = <syn::Token![::]>::parse(input)?;
|
||||
let generics: syn::AngleBracketedGenericArguments = input.parse()?;
|
||||
|
||||
if generics.args.len() != 3 {
|
||||
return Err(syn_err("Must provide 3 generic args."))
|
||||
if generics.args.len() != 4 {
|
||||
return Err(syn_err("Must provide 4 generic args."))
|
||||
}
|
||||
|
||||
let expected_types = ["VoterIndex", "TargetIndex", "Accuracy"];
|
||||
let expected_types = ["VoterIndex", "TargetIndex", "Accuracy", "MaxVoters"];
|
||||
|
||||
let mut types: Vec<syn::Type> = generics
|
||||
.args
|
||||
@@ -197,6 +212,7 @@ impl Parse for SolutionDef {
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
|
||||
let max_voters = types.pop().expect("Vector of length 4 can be popped; qed");
|
||||
let weight_type = types.pop().expect("Vector of length 3 can be popped; qed");
|
||||
let target_type = types.pop().expect("Vector of length 2 can be popped; qed");
|
||||
let voter_type = types.pop().expect("Vector of length 1 can be popped; qed");
|
||||
@@ -205,7 +221,16 @@ impl Parse for SolutionDef {
|
||||
let count_expr: syn::ExprParen = input.parse()?;
|
||||
let count = parse_parenthesized_number::<usize>(count_expr)?;
|
||||
|
||||
Ok(Self { vis, ident, voter_type, target_type, weight_type, count, compact_encoding })
|
||||
Ok(Self {
|
||||
vis,
|
||||
ident,
|
||||
voter_type,
|
||||
target_type,
|
||||
weight_type,
|
||||
max_voters,
|
||||
count,
|
||||
compact_encoding,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
|
||||
voter_type,
|
||||
target_type,
|
||||
weight_type,
|
||||
max_voters,
|
||||
compact_encoding,
|
||||
} = def;
|
||||
|
||||
@@ -178,6 +179,27 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result<TokenStream2> {
|
||||
<#ident as _feps::NposSolution>::TargetIndex,
|
||||
<#ident as _feps::NposSolution>::Accuracy,
|
||||
>;
|
||||
impl _feps::codec::MaxEncodedLen for #ident {
|
||||
fn max_encoded_len() -> usize {
|
||||
use frame_support::traits::Get;
|
||||
use _feps::codec::Encode;
|
||||
let s: u32 = #max_voters::get();
|
||||
let max_element_size =
|
||||
// the first voter..
|
||||
#voter_type::max_encoded_len()
|
||||
// #count - 1 tuples..
|
||||
.saturating_add(
|
||||
(#count - 1).saturating_mul(
|
||||
#target_type::max_encoded_len().saturating_add(#weight_type::max_encoded_len())))
|
||||
// and the last target.
|
||||
.saturating_add(#target_type::max_encoded_len());
|
||||
// The assumption is that it contains #count-1 empty elements
|
||||
// and then last element with full size
|
||||
#count
|
||||
.saturating_mul(_feps::codec::Compact(0u32).encoded_size())
|
||||
.saturating_add((s as usize).saturating_mul(max_element_size))
|
||||
}
|
||||
}
|
||||
impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident {
|
||||
type Error = _feps::Error;
|
||||
fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result<Self, Self::Error> {
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::<
|
||||
VoterIndex = u16,
|
||||
TargetIndex = u8,
|
||||
Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
use frame_election_provider_solution_type::generate_solution_type;
|
||||
|
||||
generate_solution_type!(pub struct TestSolution::<
|
||||
VoterIndex = u16,
|
||||
TargetIndex = u8,
|
||||
Accuracy = Perbill,
|
||||
ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
error: Expected binding: `MaxVoters = ...`
|
||||
--> tests/ui/fail/missing_size_bound.rs:7:2
|
||||
|
|
||||
7 | ConstU32::<10>,
|
||||
| ^^^^^^^^^^^^^^
|
||||
+1
@@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::<
|
||||
VoterIndex = u16,
|
||||
u8,
|
||||
Accuracy = Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
|
||||
@@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::<
|
||||
u16,
|
||||
TargetIndex = u8,
|
||||
Accuracy = Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::<
|
||||
u16,
|
||||
u8,
|
||||
Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
|
||||
+1
@@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::<
|
||||
TargetIndex = u16,
|
||||
VoterIndex = u8,
|
||||
Accuracy = Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8));
|
||||
|
||||
fn main() {}
|
||||
|
||||
+1
@@ -5,6 +5,7 @@ generate_solution_type!(
|
||||
VoterIndex = u8,
|
||||
TargetIndex = u16,
|
||||
Accuracy = Perbill,
|
||||
MaxVoters = ConstU32::<10>,
|
||||
>(8)
|
||||
);
|
||||
|
||||
|
||||
@@ -192,6 +192,11 @@ pub use scale_info;
|
||||
pub use sp_arithmetic;
|
||||
#[doc(hidden)]
|
||||
pub use sp_std;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
// Simple Extension trait to easily convert `None` from index closures to `Err`.
|
||||
//
|
||||
// This is only generated and re-exported for the solution code to use.
|
||||
|
||||
+9
-2
@@ -19,10 +19,16 @@
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use std::{collections::HashMap, convert::TryInto, hash::Hash, HashSet};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
convert::TryInto,
|
||||
hash::Hash,
|
||||
};
|
||||
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
|
||||
pub type AccountId = u64;
|
||||
|
||||
/// 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);
|
||||
@@ -34,13 +40,14 @@ pub fn p(p: u8) -> TestAccuracy {
|
||||
}
|
||||
|
||||
pub type MockAssignment = crate::Assignment<AccountId, TestAccuracy>;
|
||||
pub type Voter = (AccountId, VoteWeight, Vec<AccountId>);
|
||||
pub type Voter = (AccountId, crate::VoteWeight, Vec<AccountId>);
|
||||
|
||||
crate::generate_solution_type! {
|
||||
pub struct TestSolution::<
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u16,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = frame_support::traits::ConstU32::<20>,
|
||||
>(16)
|
||||
}
|
||||
|
||||
+81
-3
@@ -20,13 +20,15 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use crate::{mock::*, IndexAssignment, NposSolution};
|
||||
use frame_support::traits::ConstU32;
|
||||
use rand::SeedableRng;
|
||||
use std::convert::TryInto;
|
||||
|
||||
mod solution_type {
|
||||
use super::*;
|
||||
use codec::{Decode, Encode};
|
||||
// these need to come from the same dev-dependency `sp-npos-elections`, not from the crate.
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
// these need to come from the same dev-dependency `frame-election-provider-support`, not from
|
||||
// the crate.
|
||||
use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution};
|
||||
use sp_std::{convert::TryInto, fmt::Debug};
|
||||
|
||||
@@ -37,7 +39,12 @@ mod solution_type {
|
||||
use crate::generate_solution_type;
|
||||
generate_solution_type!(
|
||||
#[compact]
|
||||
struct InnerTestSolutionIsolated::<VoterIndex = u32, TargetIndex = u8, Accuracy = sp_runtime::Percent>(12)
|
||||
struct InnerTestSolutionIsolated::<
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u8,
|
||||
Accuracy = sp_runtime::Percent,
|
||||
MaxVoters = crate::tests::ConstU32::<20>,
|
||||
>(12)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,6 +57,7 @@ mod solution_type {
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = ConstU32::<20>,
|
||||
>(16)
|
||||
);
|
||||
let solution = InnerTestSolution {
|
||||
@@ -68,6 +76,7 @@ mod solution_type {
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = ConstU32::<20>,
|
||||
>(16)
|
||||
);
|
||||
let compact = InnerTestSolutionCompact {
|
||||
@@ -82,6 +91,75 @@ mod solution_type {
|
||||
assert!(with_compact < without_compact);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_encoded_len_too_small() {
|
||||
generate_solution_type!(
|
||||
pub struct InnerTestSolution::<
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = ConstU32::<1>,
|
||||
>(3)
|
||||
);
|
||||
let solution = InnerTestSolution {
|
||||
votes1: vec![(2, 20), (4, 40)],
|
||||
votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// We actually have 4 voters, but the bound is 1 voter, so the implemented bound is too
|
||||
// small.
|
||||
assert!(solution.encode().len() > InnerTestSolution::max_encoded_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_encoded_len_upper_bound() {
|
||||
generate_solution_type!(
|
||||
pub struct InnerTestSolution::<
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = ConstU32::<4>,
|
||||
>(3)
|
||||
);
|
||||
let solution = InnerTestSolution {
|
||||
votes1: vec![(2, 20), (4, 40)],
|
||||
votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// We actually have 4 voters, and the bound is 4 voters, so the implemented bound should be
|
||||
// larger than the encoded len.
|
||||
assert!(solution.encode().len() < InnerTestSolution::max_encoded_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_encoded_len_exact() {
|
||||
generate_solution_type!(
|
||||
pub struct InnerTestSolution::<
|
||||
VoterIndex = u32,
|
||||
TargetIndex = u32,
|
||||
Accuracy = TestAccuracy,
|
||||
MaxVoters = ConstU32::<4>,
|
||||
>(3)
|
||||
);
|
||||
let solution = InnerTestSolution {
|
||||
votes1: vec![],
|
||||
votes2: vec![],
|
||||
votes3: vec![
|
||||
(1, [(10, p(50)), (11, p(20))], 12),
|
||||
(2, [(20, p(50)), (21, p(20))], 22),
|
||||
(3, [(30, p(50)), (31, p(20))], 32),
|
||||
(4, [(40, p(50)), (41, p(20))], 42),
|
||||
],
|
||||
};
|
||||
|
||||
// We have 4 voters, the bound is 4 voters, and all the voters voted for 3 targets, which is
|
||||
// the max number of targets. This should represent the upper bound that `max_encoded_len`
|
||||
// represents.
|
||||
assert_eq!(solution.encode().len(), InnerTestSolution::max_encoded_len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solution_struct_is_codec() {
|
||||
let solution = TestSolution {
|
||||
Reference in New Issue
Block a user