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

Implements a variable deposit base calculation for EPM signed submissions #13983

Open
wants to merge 63 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
cda7c0e
Implements a variable deposit base calculation in EPM
gpestana Apr 23, 2023
cf5cd81
Implements geometric increase of base deposit based on the number of …
gpestana Apr 26, 2023
8b7e455
Finishes implementation and test
gpestana Apr 27, 2023
b9d90aa
More docs
gpestana Apr 27, 2023
95217b7
fixes docs
gpestana Apr 27, 2023
66a0bfb
Update frame/election-provider-multi-phase/src/signed.rs
gpestana May 4, 2023
a1636ca
Update bin/node/runtime/src/lib.rs
gpestana May 4, 2023
cb33150
Adds force_origin support (#13845)
gupnik Apr 24, 2023
0591d11
Vote locks for all reasons except RESERVE (#13914)
muharem Apr 24, 2023
1a14c52
[ci] Update buildah command and version (#13989)
alvicsam Apr 24, 2023
1c781d6
Bump enumflags2 from 0.7.5 to 0.7.7 (#13995)
dependabot[bot] Apr 24, 2023
e5a9709
frame-support-procedural: Fix detection of the tuples feature (#13996)
bkchr Apr 24, 2023
2d277ad
pallet-democracy: Do not request the proposal when scheduling (#13827)
bkchr Apr 25, 2023
db79585
[ci] Add message to cargo-deny (#14001)
alvicsam Apr 25, 2023
e34ab81
refactor(cli): Make some run params reusable (#13870)
yjhmelody Apr 25, 2023
c463fb0
contracts Add LOG_TARGET constant (#14002)
pgherveou Apr 25, 2023
316bced
frame-support: migrate some tests from `decl_*` macros to the new `pa…
koushiro Apr 25, 2023
4286a74
Removes ReportsByKindIndex (#13936)
gupnik Apr 25, 2023
f05447f
Allow missing docs for autogen weights. (#14011)
gilescope Apr 25, 2023
5ca19ef
[contracts] Port host functions to Weight V2 and storage deposit limi…
agryaznov Apr 26, 2023
7ddac2d
sp-core: remove useless bounded module (#13865)
koushiro Apr 26, 2023
4df1a87
fix a test (#14021)
agryaznov Apr 26, 2023
df3a709
Various minor fixes (#13945)
gavofyork Apr 26, 2023
941de8e
contracts Add storage_deposit test (#14003)
pgherveou Apr 27, 2023
76c802e
remote-externalities: batch insert key/values (#14004)
liamaharon Apr 27, 2023
9b487cd
collective pallet: sort genesis members and enforce max len constrain…
liamaharon Apr 27, 2023
db806ea
sc-network-sync: Improve error reporting (#14025)
bkchr Apr 27, 2023
5c320d3
contracts Fix store-call test path (#14028)
pgherveou Apr 27, 2023
49f69e4
chore(cli): make cli display docs correctly (#14017)
yjhmelody Apr 27, 2023
3b05747
improve staking interface methods (#14023)
kianenigma Apr 27, 2023
a5c6264
try-runtime-cli: improve ci stability (#14030)
liamaharon Apr 27, 2023
a6aa272
FRAME: inherited call weight syntax (#13932)
ggwpez Apr 27, 2023
07b1541
Implements `try_state` hook in elections and EPM pallets (#13979)
gpestana Apr 27, 2023
be73ca3
Bump wasmtime from 6.0.1 to 6.0.2 (#14037)
dependabot[bot] Apr 27, 2023
d20deb0
Improve contribution guidelines (#13902)
bkchr Apr 28, 2023
063b3b0
CI: Remove crate publish check (#14044)
ggwpez Apr 28, 2023
9ad2b40
fix(in_mem): fix the clone logic (#14038)
yjhmelody Apr 29, 2023
476ebd4
Contracts: runtime_call and storage_deposit (#13990)
pgherveou Apr 29, 2023
0e3c6b5
Fix bags-list tests execution (#14047)
bkchr May 1, 2023
513463b
CI: migrate to Google Cloud (#13994)
rcny May 2, 2023
f541d16
rpc: Use the blocks pinning API for chainHead methods (#13233)
lexnv May 2, 2023
2b3e750
contracts: Make Origin information available (#13708)
juangirini May 2, 2023
6ebcadd
Bump clap to 4.2.5 (#14061)
May 2, 2023
e296263
Manual seal delayed finalize (#13999)
shunsukew May 2, 2023
7eeb77d
Don't run `check-crates-publishing` job on prs (#14064)
May 3, 2023
dcaebbe
Only calculate tree route during finalization when there are multiple…
skunert May 3, 2023
4b7f99b
test-staking-e2e: Add to main `Cargo.toml`. (#14062)
bkchr May 3, 2023
89acf47
contracts: add events to ContractResult (#13807)
juangirini May 3, 2023
5f8f249
rpc server: break legacy CLI options and remove "backward compatible …
niklasad1 May 3, 2023
2668e9a
Makes DepositCalculator trait more generic
gpestana May 4, 2023
792ecc4
Merge branch 'master' into gpestana/epm_variable_deposit_base
gpestana May 4, 2023
1960329
Fixed runtime configs
gpestana May 4, 2023
afb016d
Merge remote-tracking branch 'origin/master' into gpestana/epm_variab…
May 4, 2023
1558456
Updates e2e integration tests mock
gpestana May 4, 2023
310a116
Merge remote-tracking branch 'origin/master' into gpestana/epm_variab…
May 17, 2023
0098612
Refactors the traits
gpestana May 17, 2023
99622a5
Update frame/election-provider-multi-phase/src/signed.rs
gpestana May 21, 2023
49e053b
Merge branch 'master' into gpestana/epm_variable_deposit_base
gpestana Jun 9, 2023
f053577
Nits and addresses review comments
gpestana Jun 9, 2023
f11d312
Merge branch 'master' into gpestana/epm_variable_deposit_base
gpestana Jun 9, 2023
856828e
".git/.scripts/commands/fmt/fmt.sh"
Jun 9, 2023
fe49a49
Parameterize GeometricProgression instead of using dedicated associat…
gpestana Jun 12, 2023
a51a3eb
Merge branch 'master' into gpestana/epm_variable_deposit_base
gpestana Aug 10, 2023
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
Prev Previous commit
Next Next commit
Implements try_state hook in elections and EPM pallets (#13979)
* Adds try_state hook to elections pallets

* Addresses PR review comments

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>

* remove unecessary println

* ensures try-runtime does not mutate storage

* Addresses PR comments

* Fixes snapshot invariant checks; simplifies test infra

---------

Co-authored-by: Liam Aharon <liam.aharon@hotmail.com>
Co-authored-by: parity-processbot <>
  • Loading branch information
gpestana and liamaharon committed May 4, 2023
commit 07b15411fdb5190e292bb6b7c1b721d7757d3304
95 changes: 95 additions & 0 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,11 @@ pub mod pallet {
// configure this pallet.
assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get());
}

#[cfg(feature = "try-runtime")]
fn try_state(_n: T::BlockNumber) -> Result<(), &'static str> {
Self::do_try_state()
}
}

#[pallet::call]
Expand Down Expand Up @@ -1264,6 +1269,8 @@ pub mod pallet {
pub type CurrentPhase<T: Config> = StorageValue<_, Phase<T::BlockNumber>, ValueQuery>;

/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
///
/// Always sorted by score.
#[pallet::storage]
#[pallet::getter(fn queued_solution)]
pub type QueuedSolution<T: Config> =
Expand Down Expand Up @@ -1582,6 +1589,89 @@ impl<T: Config> Pallet<T> {
}
}

#[cfg(feature = "try-runtime")]
impl<T: Config> Pallet<T> {
fn do_try_state() -> Result<(), &'static str> {
Self::try_state_snapshot()?;
Self::try_state_signed_submissions_map()?;
Self::try_state_phase_off()
}

// [`Snapshot`] state check. Invariants:
// - [`DesiredTargets`] exists if and only if [`Snapshot`] is present.
// - [`SnapshotMetadata`] exist if and only if [`Snapshot`] is present.
fn try_state_snapshot() -> Result<(), &'static str> {
if <Snapshot<T>>::exists() &&
<SnapshotMetadata<T>>::exists() &&
<DesiredTargets<T>>::exists()
{
Ok(())
} else if !<Snapshot<T>>::exists() &&
!<SnapshotMetadata<T>>::exists() &&
!<DesiredTargets<T>>::exists()
{
Ok(())
} else {
Err("If snapshot exists, metadata and desired targets should be set too. Otherwise, none should be set.")
}
}

// [`SignedSubmissionsMap`] state check. Invariants:
// - All [`SignedSubmissionIndices`] are present in [`SignedSubmissionsMap`], and no more;
// - [`SignedSubmissionNextIndex`] is not present in [`SignedSubmissionsMap`];
// - [`SignedSubmissionIndices`] is sorted by election score.
fn try_state_signed_submissions_map() -> Result<(), &'static str> {
let mut last_score: ElectionScore = Default::default();
let indices = <SignedSubmissionIndices<T>>::get();

for (i, indice) in indices.iter().enumerate() {
let submission = <SignedSubmissionsMap<T>>::get(indice.2);
if submission.is_none() {
return Err("All signed submissions indices must be part of the submissions map")
}

if i == 0 {
last_score = indice.0
} else {
if last_score.strict_threshold_better(indice.0, Perbill::zero()) {
return Err("Signed submission indices vector must be ordered by election score")
}
last_score = indice.0;
}
}

if <SignedSubmissionsMap<T>>::iter().nth(indices.len()).is_some() {
return Err("Signed submissions map length should be the same as the indices vec length")
}

match <SignedSubmissionNextIndex<T>>::get() {
0 => Ok(()),
next =>
if <SignedSubmissionsMap<T>>::get(next).is_some() {
return Err(
"The next submissions index should not be in the submissions maps already",
)
} else {
Ok(())
},
}
}

// [`Phase::Off`] state check. Invariants:
// - If phase is `Phase::Off`, [`Snapshot`] must be none.
fn try_state_phase_off() -> Result<(), &'static str> {
match Self::current_phase().is_off() {
false => Ok(()),
true =>
if <Snapshot<T>>::get().is_some() {
Err("Snapshot must be none when in Phase::Off")
} else {
Ok(())
},
}
}
}

impl<T: Config> ElectionProviderBase for Pallet<T> {
type AccountId = T::AccountId;
type BlockNumber = T::BlockNumber;
Expand Down Expand Up @@ -1654,6 +1744,11 @@ mod feasibility_check {
MultiPhase::feasibility_check(solution, COMPUTE),
FeasibilityError::SnapshotUnavailable
);

// kill also `SnapshotMetadata` and `DesiredTargets` for the storage state to be
// consistent for the try_state checks to pass.
<SnapshotMetadata<Runtime>>::kill();
<DesiredTargets<Runtime>>::kill();
})
}

Expand Down
12 changes: 11 additions & 1 deletion frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -640,7 +640,17 @@ impl ExtBuilder {
}

pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
self.build().execute_with(test)
sp_tracing::try_init_simple();

let mut ext = self.build();
ext.execute_with(test);

#[cfg(feature = "try-runtime")]
ext.execute_with(|| {
assert_ok!(<MultiPhase as frame_support::traits::Hooks<u64>>::try_state(
System::block_number()
));
});
}
}

Expand Down
5 changes: 5 additions & 0 deletions frame/election-provider-multi-phase/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,11 @@ mod tests {
MultiPhase::submit(RuntimeOrigin::signed(10), Box::new(solution)),
Error::<Runtime>::PreDispatchEarlySubmission,
);

// make sure invariants hold true and post-test try state checks to pass.
<crate::Snapshot<Runtime>>::kill();
<crate::SnapshotMetadata<Runtime>>::kill();
<crate::DesiredTargets<Runtime>>::kill();
})
}

Expand Down
156 changes: 112 additions & 44 deletions frame/elections-phragmen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,11 @@ pub mod pallet {
T::MaxVotesPerVoter::get(),
);
}

#[cfg(feature = "try-runtime")]
fn try_state(_n: T::BlockNumber) -> Result<(), &'static str> {
Self::do_try_state()
}
}

#[pallet::call]
Expand Down Expand Up @@ -893,7 +898,7 @@ impl<T: Config> Pallet<T> {
}

/// Get the members' account ids.
fn members_ids() -> Vec<T::AccountId> {
pub(crate) fn members_ids() -> Vec<T::AccountId> {
Self::members().into_iter().map(|m| m.who).collect::<Vec<T::AccountId>>()
}

Expand Down Expand Up @@ -1192,6 +1197,104 @@ impl<T: Config> ContainsLengthBound for Pallet<T> {
}
}

#[cfg(any(feature = "try-runtime", test))]
impl<T: Config> Pallet<T> {
fn do_try_state() -> Result<(), &'static str> {
Self::try_state_members()?;
Self::try_state_runners_up()?;
Self::try_state_candidates()?;
Self::try_state_candidates_runners_up_disjoint()?;
Self::try_state_members_disjoint()?;
Self::try_state_members_approval_stake()
}

/// [`Members`] state checks. Invariants:
/// - Members are always sorted based on account ID.
fn try_state_members() -> Result<(), &'static str> {
let mut members = Members::<T>::get().clone();
members.sort_by_key(|m| m.who.clone());

if Members::<T>::get() == members {
Ok(())
} else {
Err("try_state checks: Members must be always sorted by account ID")
}
}

// [`RunnersUp`] state checks. Invariants:
// - Elements are sorted based on weight (worst to best).
fn try_state_runners_up() -> Result<(), &'static str> {
let mut sorted = RunnersUp::<T>::get();
// worst stake first
sorted.sort_by(|a, b| a.stake.cmp(&b.stake));

if RunnersUp::<T>::get() == sorted {
Ok(())
} else {
Err("try_state checks: Runners Up must always be sorted by stake (worst to best)")
}
}

// [`Candidates`] state checks. Invariants:
// - Always sorted based on account ID.
fn try_state_candidates() -> Result<(), &'static str> {
let mut candidates = Candidates::<T>::get().clone();
candidates.sort_by_key(|(c, _)| c.clone());

if Candidates::<T>::get() == candidates {
Ok(())
} else {
Err("try_state checks: Candidates must be always sorted by account ID")
}
}
// [`Candidates`] and [`RunnersUp`] state checks. Invariants:
// - Candidates and runners-ups sets are disjoint.
fn try_state_candidates_runners_up_disjoint() -> Result<(), &'static str> {
match Self::intersects(&Self::candidates_ids(), &Self::runners_up_ids()) {
true => Err("Candidates and runners up sets should always be disjoint"),
false => Ok(()),
}
}

// [`Members`], [`Candidates`] and [`RunnersUp`] state checks. Invariants:
// - Members and candidates sets are disjoint;
// - Members and runners-ups sets are disjoint.
fn try_state_members_disjoint() -> Result<(), &'static str> {
match Self::intersects(&Pallet::<T>::members_ids(), &Self::candidates_ids()) &&
Self::intersects(&Pallet::<T>::members_ids(), &Self::runners_up_ids())
{
true => Err("Members set should be disjoint from candidates and runners-up sets"),
false => Ok(()),
}
}

// [`Members`], [`RunnersUp`] and approval stake state checks. Invariants:
// - Selected members should have approval stake;
// - Selected RunnersUp should have approval stake.
fn try_state_members_approval_stake() -> Result<(), &'static str> {
match Members::<T>::get()
.iter()
.chain(RunnersUp::<T>::get().iter())
.all(|s| s.stake != BalanceOf::<T>::zero())
{
true => Ok(()),
false => Err("Members and RunnersUp must have approval stake"),
}
}

fn intersects<P: PartialEq>(a: &[P], b: &[P]) -> bool {
a.iter().any(|e| b.contains(e))
}

fn candidates_ids() -> Vec<T::AccountId> {
Pallet::<T>::candidates().iter().map(|(x, _)| x).cloned().collect::<Vec<_>>()
}

fn runners_up_ids() -> Vec<T::AccountId> {
Pallet::<T>::runners_up().into_iter().map(|r| r.who).collect::<Vec<_>>()
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -1418,7 +1521,13 @@ mod tests {
.into();
ext.execute_with(pre_conditions);
ext.execute_with(test);
ext.execute_with(post_conditions)

#[cfg(feature = "try-runtime")]
ext.execute_with(|| {
assert_ok!(<Elections as frame_support::traits::Hooks<u64>>::try_state(
System::block_number()
));
});
}
}

Expand Down Expand Up @@ -1475,54 +1584,13 @@ mod tests {
.unwrap_or_default()
}

fn intersects<T: PartialEq>(a: &[T], b: &[T]) -> bool {
a.iter().any(|e| b.contains(e))
}

fn ensure_members_sorted() {
let mut members = Elections::members().clone();
members.sort_by_key(|m| m.who);
assert_eq!(Elections::members(), members);
}

fn ensure_candidates_sorted() {
let mut candidates = Elections::candidates().clone();
candidates.sort_by_key(|(c, _)| *c);
assert_eq!(Elections::candidates(), candidates);
}

fn locked_stake_of(who: &u64) -> u64 {
Voting::<Test>::get(who).stake
}

fn ensure_members_has_approval_stake() {
// we filter members that have no approval state. This means that even we have more seats
// than candidates, we will never ever chose a member with no votes.
assert!(Elections::members()
.iter()
.chain(Elections::runners_up().iter())
.all(|s| s.stake != u64::zero()));
}

fn ensure_member_candidates_runners_up_disjoint() {
// members, candidates and runners-up must always be disjoint sets.
assert!(!intersects(&members_ids(), &candidate_ids()));
assert!(!intersects(&members_ids(), &runners_up_ids()));
assert!(!intersects(&candidate_ids(), &runners_up_ids()));
}

fn pre_conditions() {
System::set_block_number(1);
ensure_members_sorted();
ensure_candidates_sorted();
ensure_member_candidates_runners_up_disjoint();
}

fn post_conditions() {
ensure_members_sorted();
ensure_candidates_sorted();
ensure_member_candidates_runners_up_disjoint();
ensure_members_has_approval_stake();
Elections::do_try_state().unwrap();
}

fn submit_candidacy(origin: RuntimeOrigin) -> sp_runtime::DispatchResult {
Expand Down