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

Use a better data structure for SignedSubmissions instead of Vec #8933

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
14fcad9
Remove: (#8748)
kpp May 27, 2021
27cc30e
CI: fix simnet trigger (#8927)
TriplEight May 27, 2021
925f170
More sc-service config reexports (#8887)
MOZGIII May 27, 2021
426d57b
Fix check runtime CI (#8930)
athei May 28, 2021
c2ec5bc
Bump parity-wasm and pwasm-utils to the newest versions everywhere (#…
athei May 28, 2021
1d03d4e
BROKEN: convert SignedSubmissions to BoundedBTreeSet
coriolinus May 28, 2021
050e4ac
Simple `MaxBoundedLen` Implementations (#8793)
shawntabrizi May 28, 2021
6a63f28
remove duplicate Issued/Burned events (#8935)
joepetrowski May 28, 2021
4b99c7f
weather -> whether (#8938)
semuelle May 28, 2021
ac277db
make remote ext use batch ws-client (#8916)
kianenigma May 29, 2021
6aaa031
Make `Schedule` fields public to allow for customization (#8924)
athei May 29, 2021
be1b8ef
Session key should be settable at genesis even for non-endowed accoun…
gavofyork May 29, 2021
88021e9
Migrate pallet-scored-pool to pallet attribute macro (#8825)
shaunxw May 31, 2021
87e63fa
Bump retain_mut from 0.1.2 to 0.1.3 (#8951)
dependabot[bot] May 31, 2021
7209f7c
Merge remote-tracking branch 'origin/kiz-election-provider-3-signed-p…
coriolinus May 31, 2021
61aa8dc
Use correct CreateInherentDataProviders impl for manual seal (#8852)
May 31, 2021
fc29e14
Refactor code a little bit (#8932)
kpp May 31, 2021
6d43761
Optimize `next_storage_key` (#8956)
bkchr May 31, 2021
2538839
Add deserialize for TransactionValidityError in std. (#8961)
kianenigma May 31, 2021
7ba4e4c
Bump getrandom from 0.2.2 to 0.2.3 (#8952)
dependabot[bot] May 31, 2021
da051b1
Allow usage of path in construct_runtime! (#8801)
KiChjang Jun 1, 2021
f87609a
Reduce cargo doc warnings (#8947)
gilescope Jun 1, 2021
5ceb0b0
Update wasmtime to 0.27 (#8913)
pepyakin Jun 1, 2021
e1d93fb
Spellling corrections (no code changes) (#8971)
gilescope Jun 1, 2021
04aa0e9
Dependabot use correct label (#8973)
bkchr Jun 1, 2021
408e803
Inject hashed prefix for remote-ext (#8960)
kianenigma Jun 1, 2021
bfef07c
Use `SpawnTaskHandle`s for spawning tasks in the tx pool (#8958)
expenses Jun 1, 2021
24750ea
Do not spend time on verifying the signatures before calling Runtime …
pepyakin Jun 1, 2021
fa23b18
Revert "Use `SpawnTaskHandle`s for spawning tasks in the tx pool (#8…
expenses Jun 1, 2021
d8b3fce
Uniques: An economically-secure basic-featured NFT pallet (#8813)
gavofyork Jun 1, 2021
45f1630
Update WeakBoundedVec's remove and swap_remove (#8985)
Jun 1, 2021
9f621a9
Convert another instance of Into impl to From in the macros (#8986)
MOZGIII Jun 1, 2021
4652f9e
also fix bounded vec (#8987)
shawntabrizi Jun 2, 2021
b2cdfe8
fix most compiler errors
coriolinus Jun 2, 2021
dcf24c7
extract type SignedSubmissionsOf<T>
coriolinus Jun 2, 2021
33e6e45
impl Decode bounds on BoundedBTreeMap/Set on T, not predecessor
coriolinus Jun 2, 2021
1990b81
fix recursive trait bound problem
coriolinus Jun 2, 2021
a8069e5
minor fixes
coriolinus Jun 2, 2021
d961e2f
more little fixes
coriolinus Jun 2, 2021
1ab8cf2
correct semantics for try_insert
coriolinus Jun 2, 2021
d5724a2
more fixes
coriolinus Jun 2, 2021
9b4f143
derive Ord for SolutionType
coriolinus Jun 2, 2021
997a469
tests compile
coriolinus Jun 2, 2021
35277ea
fix most tests, rm unnecessary one
coriolinus Jun 2, 2021
538b15f
Transactionpool: Make `ready_at` return earlier (#8995)
bkchr Jun 2, 2021
437c838
Discard notifications if we have failed to parse handshake (#8806)
tomaka Jun 2, 2021
94679eb
Migrate pallet-democracy to pallet attribute macro (#8824)
shaunxw Jun 3, 2021
48aea1b
Add ecdsa::Pair::verify_prehashed() (#8996)
adoerr Jun 3, 2021
b14fdf5
Non-fungible token traits (#8993)
gavofyork Jun 3, 2021
2562dda
Removes unused import (#9007)
0x7CFE Jun 3, 2021
d6e4db6
Add Call Filter That Prevents Nested `batch_all` (#9009)
shawntabrizi Jun 3, 2021
ea5d357
Transaction pool: Ensure that we prune transactions properly (#8963)
bkchr Jun 3, 2021
a57bc44
Storage chain: Runtime module (#8624)
arkpar Jun 4, 2021
7d8a9b6
more useful error message (#9014)
ordian Jun 4, 2021
0495ead
Named reserve (#7778)
xlc Jun 4, 2021
e98aca3
update ss58 type to u16 (#8955)
jak-pan Jun 4, 2021
d27dea9
Fixed build (#9021)
arkpar Jun 4, 2021
2cff60c
Bump parity-db (#9024)
adoerr Jun 4, 2021
37bb3ae
consensus: handle justification sync for blocks authored locally (#8698)
andresilva Jun 4, 2021
24a92c3
arithmetic: fix PerThing pow (#9030)
andresilva Jun 6, 2021
1085a90
Compact proof utilities in sp_trie. (#8574)
cheme Jun 7, 2021
1fa8cf7
Don't inlucde nominaotrs that back no one in the snapshot. (#9017)
kianenigma Jun 7, 2021
a8e3f36
fix all_in_one test which had a logic error
coriolinus Jun 7, 2021
8190214
use sp_std, not std
coriolinus Jun 7, 2021
5d89967
Periodically call `Peerset::alloc_slots` on all sets (#9025)
tomaka Jun 7, 2021
fa26ce6
contracts: Add new `seal_call` that offers new features (#8909)
athei Jun 7, 2021
d089179
fix unreserve_all_named (#9042)
xlc Jun 8, 2021
0af6df5
Delete legacy runtime metadata macros (#9043)
ascjones Jun 8, 2021
0a2472d
`rpc-http-threads` cli arg (#8890)
tgmichel Jun 8, 2021
4ec3a3a
allow inserting equal items into bounded map/set
coriolinus Jun 8, 2021
3d7dc0b
refactor: only load one solution at a time
coriolinus Jun 8, 2021
07a1af3
Emit `Bonded` event when rebonding (#9040)
shawntabrizi Jun 8, 2021
dae31f2
fix tests
coriolinus Jun 8, 2021
de92b1e
Merge remote-tracking branch 'origin/master' into prgn-election-provi…
coriolinus Jun 8, 2021
95d7324
Revert "Merge remote-tracking branch 'origin/master' into prgn-electi…
coriolinus Jun 8, 2021
e614849
Merge remote-tracking branch 'origin/kiz-election-provider-3-signed-p…
coriolinus Jun 8, 2021
847b98c
only derive debug when std
coriolinus Jun 8, 2021
5b2a7c5
write after check
coriolinus Jun 8, 2021
7f4a837
SignedSubmissions doesn't ever modify storage until .put()
coriolinus Jun 9, 2021
3317a4b
REVERT ME: demo that Drop impl doesn't work
coriolinus Jun 9, 2021
8313f58
Revert "REVERT ME: demo that Drop impl doesn't work"
coriolinus Jun 9, 2021
58c96b4
doc note about decode_len
coriolinus Jun 14, 2021
9388c2c
rename get_submission, take_submission for clarity
coriolinus Jun 17, 2021
a8e0441
add test which fails for current incorrect behavior
coriolinus Jun 17, 2021
7dfc184
inline fn insert_submission
coriolinus Jun 17, 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
23 changes: 10 additions & 13 deletions frame/election-provider-multi-phase/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,19 +273,16 @@ frame_benchmarking::benchmarks! {
MultiPhase::<T>::on_initialize_open_signed().expect("should be ok to start signed phase");
<Round<T>>::put(1);

<SignedSubmissions<T>>::mutate(|queue| {
for i in 0..c {
let solution = RawSolution {
score: [(10_000_000 + i).into(), 0, 0],
..Default::default()
};
let signed_submission = SignedSubmission { solution, ..Default::default() };
queue.push(signed_submission);
}
// as of here, the queue is ordered worst-to-best.
// However, we have an invariant that it should be ordered best-to-worst
queue.reverse();
});
let mut signed_submissions = SignedSubmissions::<T>::get();
for i in 0..c {
let solution = RawSolution {
score: [(10_000_000 + i).into(), 0, 0],
..Default::default()
};
let signed_submission = SignedSubmission { solution, ..Default::default() };
signed_submissions.insert(signed_submission);
}
signed_submissions.put();

let caller = frame_benchmarking::whitelisted_caller();
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() * 10u32.into());
Expand Down
91 changes: 65 additions & 26 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,16 @@ pub mod helpers;

const LOG_TARGET: &'static str = "runtime::election-provider";

pub mod unsigned;
pub mod signed;
pub mod unsigned;
pub mod weights;

pub use signed::{
BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf,
SignedSubmissions, SubmissionIndicesOf,
};
pub use weights::WeightInfo;

pub use signed::{SignedSubmission, BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf};

/// The compact solution type used by this crate.
pub type CompactOf<T> = <T as Config>::CompactSolution;

Expand Down Expand Up @@ -387,7 +389,7 @@ impl Default for ElectionCompute {
///
/// Such a solution should never become effective in anyway before being checked by the
/// `Pallet::feasibility_check`
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)]
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, PartialOrd, Ord)]
pub struct RawSolution<C> {
/// Compact election edges.
pub compact: C,
Expand Down Expand Up @@ -562,6 +564,7 @@ pub mod pallet {
/// Maximum number of signed submissions that can be queued.
#[pallet::constant]
type SignedMaxSubmissions: Get<u32>;

/// Maximum weight of a signed solution.
///
/// This should probably be similar to [`Config::MinerMaxWeight`].
Expand Down Expand Up @@ -603,6 +606,7 @@ pub mod pallet {
+ Eq
+ Clone
+ sp_std::fmt::Debug
+ Ord
+ CompactSolution;

/// Accuracy used for fallback on-chain election.
Expand Down Expand Up @@ -879,22 +883,37 @@ pub mod pallet {
Error::<T>::SignedTooMuchWeight,
);

// ensure solution claims is better.
// create the submission
let reward = T::SignedRewardBase::get();
let deposit = Self::deposit_for(&solution, size);
let submission = SignedSubmission { who: who.clone(), deposit, reward, solution };

// insert the submission if the queue has space or it's better than the weakest
// eject the weakest if the queue was full
let mut signed_submissions = Self::signed_submissions();
let ejected_a_solution = signed_submissions.len()
== T::SignedMaxSubmissions::get().saturated_into::<usize>();
let index = Self::insert_submission(&who, &mut signed_submissions, solution, size)
.ok_or(Error::<T>::SignedQueueFull)?;

// collect deposit. Thereafter, the function cannot fail.
let deposit = signed_submissions
.get(index)
.map(|s| s.deposit)
.ok_or(Error::<T>::InvalidSubmissionIndex)?;
T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;

// store the new signed submission.
<SignedSubmissions<T>>::put(signed_submissions);
let (inserted, maybe_weakest) = signed_submissions.insert(submission);
let ejected_a_solution = maybe_weakest.is_some();

// it's an error if we neither inserted nor removed any submissions: this indicates
Copy link
Contributor

@kianenigma kianenigma Jun 17, 2021

Choose a reason for hiding this comment

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

WDYT? for me this is much easier to reason about, even if it is a few more lines of code:

match (inserted, maybe_weakest)) => {
  (false, None) => { // Not inserted, none removed }
  (false, Some(weakest) => { unreachable!()??? }
  (true, None) => { // inserted and none was removed } 
  (true, Some(weakest) => { // inserted and someone was ejected }
}

which can be made into

match (inserted, maybe_weakest)) => {
  (false, None) => { // Not inserted, none removed }
  (false, Some(weakest) => { unreachable!()??? }
  (true, maybe_removed) => { // inserted, maybe unreserve if maybe_removed.is_some() }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Doesn't that break down into what precisely exists now? We handle the (false, None) case with an ensure, don't need to handle a (false, Some(_)) unreachable case, and then handle the (true, maybe_removed) case inline. Agree that those are the cases we need to consider; I just think that the existing formulation is easier to read.

Copy link
Contributor

Choose a reason for hiding this comment

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

I read this in the downstream PR again and it was still confusing to me. Probably our definitions of "easy to read" differs. For me a readable code is one that can be easily understood with the least amount of time spent reading it. This entails being slightly verbose sometimes.

A match statement's basis is that it cannot have any unhandled cases, and the reader can clearly see each case. The sequence of if statements took more time to reason about, at least in my case.

I leave it up to you though, just wanted to share my detailed opinion.

// the queue was full but our solution had insufficient score to eject any solution
ensure!(
(false, false) != (inserted, ejected_a_solution),
Error::<T>::SignedQueueFull,
);

if inserted {
// collect deposit. Thereafter, the function cannot fail.
T::Currency::reserve(&who, deposit)
.map_err(|_| Error::<T>::SignedCannotPayDeposit)?;
}

// if we had to remove the weakest solution, unreserve its deposit
if let Some(weakest) = maybe_weakest {
let _remainder = T::Currency::unreserve(&weakest.who, weakest.deposit);
debug_assert!(_remainder.is_zero());
}

signed_submissions.put();
Self::deposit_event(Event::SolutionStored(ElectionCompute::Signed, ejected_a_solution));
Ok(())
}
Expand Down Expand Up @@ -1048,14 +1067,34 @@ pub mod pallet {
#[pallet::getter(fn snapshot_metadata)]
pub type SnapshotMetadata<T: Config> = StorageValue<_, SolutionOrSnapshotSize>;

/// Sorted (worse -> best) list of unchecked, signed solutions.
/// The next index to be assigned to an incoming signed submission.
///
/// We can't just use `SignedSubmissionIndices.len()`, because that's a bounded set; past its
/// capacity, it will simply saturate. We can't just iterate over `SignedSubmissionsMap`,
/// because iteration is slow. Instead, we store the value here.
#[pallet::storage]
pub(crate) type SignedSubmissionNextIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
coriolinus marked this conversation as resolved.
Show resolved Hide resolved

/// A sorted, bounded set of `(score, index)`, where each `index` points to a value in
/// `SignedSubmissions`.
///
/// We never need to process more than a single signed submission at a time. Signed submissions
/// can be quite large, so we're willing to pay the cost of multiple database accesses to access
/// them one at a time instead of reading and decoding all of them at once.
#[pallet::storage]
pub(crate) type SignedSubmissionIndices<T: Config> =
StorageValue<_, SubmissionIndicesOf<T>, ValueQuery>;

/// Unchecked, signed solutions.
///
/// Together with `SubmissionIndices`, this stores a bounded set of `SignedSubmissions` while
/// allowing us to keep only a single one in memory at a time.
///
/// Twox note: the key of the map is an auto-incrementing index which users cannot inspect or
/// affect; we shouldn't need a cryptographically secure hasher.
#[pallet::storage]
#[pallet::getter(fn signed_submissions)]
pub type SignedSubmissions<T: Config> = StorageValue<
_,
Vec<SignedSubmission<T::AccountId, BalanceOf<T>, CompactOf<T>>>,
ValueQuery,
>;
pub(crate) type SignedSubmissionsMap<T: Config> =
StorageMap<_, Twox64Concat, u32, SignedSubmissionOf<T>, ValueQuery>;

/// The minimum score that each 'untrusted' solution must attain in order to be considered
/// feasible.
Expand Down
Loading