Skip to content

Commit

Permalink
binary search by account id
Browse files Browse the repository at this point in the history
  • Loading branch information
Chralt98 committed Mar 6, 2023
1 parent b218d56 commit c04d5ff
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 103 deletions.
149 changes: 67 additions & 82 deletions zrml/court/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub use types::*;
mod pallet {
use crate::{
weights::WeightInfoZeitgeist, AppealInfo, CourtInfo, CourtPalletApi, CourtStatus, Draw,
ExitRequest, JurorInfo, JurorPoolItem, Periods, Vote,
ExitRequest, JurorPoolItem, Periods, Vote,
};
use alloc::{
collections::{BTreeMap, BTreeSet},
Expand All @@ -50,7 +50,7 @@ mod pallet {
use core::marker::PhantomData;
use frame_support::{
dispatch::DispatchResult,
ensure, log,
ensure,
pallet_prelude::{Hooks, OptionQuery, StorageMap, StorageValue, ValueQuery, Weight},
traits::{
Currency, Get, Imbalance, IsType, LockIdentifier, LockableCurrency,
Expand Down Expand Up @@ -195,7 +195,6 @@ mod pallet {
pub(crate) type AccountIdLookupOf<T> =
<<T as frame_system::Config>::Lookup as StaticLookup>::Source;
pub(crate) type CourtOf<T> = CourtInfo<<T as frame_system::Config>::BlockNumber, AppealsOf<T>>;
pub(crate) type JurorInfoOf<T> = JurorInfo<BalanceOf<T>>;
pub(crate) type JurorPoolItemOf<T> = JurorPoolItem<AccountIdOf<T>, BalanceOf<T>>;
pub(crate) type JurorPoolOf<T> = BoundedVec<JurorPoolItemOf<T>, <T as Config>::MaxJurors>;
pub(crate) type DrawOf<T> =
Expand All @@ -215,8 +214,7 @@ mod pallet {

/// The general information about each juror.
#[pallet::storage]
pub type Jurors<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, JurorInfoOf<T>, OptionQuery>;
pub type Jurors<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, (), OptionQuery>;

/// If a juror wants to exit the court, this information is required for a multiblock execution.
#[pallet::storage]
Expand Down Expand Up @@ -332,9 +330,6 @@ mod pallet {
VoteAlreadyDenounced,
/// A juror tried to denounce herself.
SelfDenounceDisallowed,
/// In order to execute the binary search efficiently
/// the join amount must be unqiue for each juror.
AmountAlreadyUsed,
/// The court is not in the closed state.
CourtNotClosed,
/// The jurors were already reassigned.
Expand Down Expand Up @@ -399,58 +394,57 @@ mod pallet {

let mut jurors = JurorPool::<T>::get();

let slashed = if let Some(prev_juror_info) = <Jurors<T>>::get(&who) {
ensure!(amount > prev_juror_info.stake, Error::<T>::AmountBelowLastJoin);
let (index, pool_item) = Self::get_pool_item(&jurors, prev_juror_info.stake)
.ok_or(Error::<T>::JurorNeedsToExit)?;
let slashed = pool_item.slashed;
jurors.remove(index);
slashed
} else {
if jurors.is_full() {
let lowest_juror = jurors
.first()
.map(|pool_item| pool_item.stake)
.unwrap_or_else(<BalanceOf<T>>::zero);
debug_assert!({
let mut sorted = jurors.clone();
sorted.sort_by_key(|pool_item| pool_item.stake);
jurors.len() == sorted.len()
&& jurors
.iter()
.zip(sorted.iter())
.all(|(a, b)| lowest_juror <= a.stake && a == b)
});
ensure!(amount > lowest_juror, Error::<T>::AmountBelowLowestJuror);
// remove the lowest staked juror
jurors.remove(0);
match jurors.binary_search_by_key(&who, |pool_item| pool_item.juror.clone()) {
Ok(i) => {
ensure!(amount > jurors[i].stake, Error::<T>::AmountBelowLastJoin);
jurors[i].stake = amount;
}
<BalanceOf<T>>::zero()
};
Err(i) => {
ensure!(!<Jurors<T>>::contains_key(&who), Error::<T>::JurorNeedsToExit);
if jurors.is_full() {
let lowest_juror = jurors
.first()
.map(|pool_item| pool_item.stake)
.unwrap_or_else(<BalanceOf<T>>::zero);
debug_assert!({
let mut sorted = jurors.clone();
sorted.sort_by_key(|pool_item| pool_item.stake);
jurors.len() == sorted.len()
&& jurors
.iter()
.zip(sorted.iter())
.all(|(a, b)| lowest_juror <= a.stake && a == b)
});
ensure!(amount > lowest_juror, Error::<T>::AmountBelowLowestJuror);
// remove the lowest staked juror
jurors.remove(0);
}

match jurors.binary_search_by_key(&amount, |pool_item| pool_item.stake) {
// The reason for this error is that each amount has a clear juror
// binary_search_by_key could otherwise return an index of an unwanted juror
// if there are multiple jurors with the same stake
Ok(_) => return Err(Error::<T>::AmountAlreadyUsed.into()),
Err(i) => jurors
.try_insert(i, JurorPoolItem { stake: amount, juror: who.clone(), slashed })
.map_err(|_| {
debug_assert!(
false,
"This should never happen, because we are removing the lowest staked \
juror above."
);
Error::<T>::MaxJurorsReached
})?,
jurors
.try_insert(
i,
JurorPoolItem {
stake: amount,
juror: who.clone(),
slashed: <BalanceOf<T>>::zero(),
},
)
.map_err(|_| {
debug_assert!(
false,
"This should never happen, because we are removing the lowest \
staked juror above."
);
Error::<T>::MaxJurorsReached
})?;
}
};

T::Currency::set_lock(T::CourtLockId::get(), &who, amount, WithdrawReasons::all());

JurorPool::<T>::put(jurors);

let juror_info = JurorInfoOf::<T> { stake: amount };
<Jurors<T>>::insert(&who, juror_info);
<Jurors<T>>::insert(&who, ());

Self::deposit_event(Event::JurorJoined { juror: who });
Ok(())
Expand All @@ -468,11 +462,11 @@ mod pallet {
pub fn prepare_exit_court(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;

let prev_juror_info = <Jurors<T>>::get(&who).ok_or(Error::<T>::JurorDoesNotExists)?;
ensure!(<Jurors<T>>::contains_key(&who), Error::<T>::JurorDoesNotExists);

let mut jurors = JurorPool::<T>::get();

if let Some((index, _)) = Self::get_pool_item(&jurors, prev_juror_info.stake) {
if let Some((index, _)) = Self::get_pool_item(&jurors, &who) {
jurors.remove(index);
<JurorPool<T>>::put(jurors);
} else {
Expand Down Expand Up @@ -507,9 +501,9 @@ mod pallet {

let juror = T::Lookup::lookup(juror)?;

let prev_juror_info = <Jurors<T>>::get(&juror).ok_or(Error::<T>::JurorDoesNotExists)?;
ensure!(<Jurors<T>>::contains_key(&juror), Error::<T>::JurorDoesNotExists);
ensure!(
Self::get_pool_item(&JurorPool::<T>::get(), prev_juror_info.stake).is_none(),
Self::get_pool_item(&JurorPool::<T>::get(), &juror).is_none(),
Error::<T>::JurorNotPreparedToExit
);

Expand Down Expand Up @@ -629,7 +623,7 @@ mod pallet {

ensure!(denouncer != juror, Error::<T>::SelfDenounceDisallowed);

let prev_juror_info = <Jurors<T>>::get(&juror).ok_or(Error::<T>::JurorDoesNotExists)?;
ensure!(<Jurors<T>>::contains_key(&juror), Error::<T>::JurorDoesNotExists);

let court = <Courts<T>>::get(market_id).ok_or(Error::<T>::CourtNotFound)?;
let now = <frame_system::Pallet<T>>::block_number();
Expand Down Expand Up @@ -669,7 +663,7 @@ mod pallet {
T::Currency::resolve_creating(&reward_pot, imbalance);

let mut jurors = JurorPool::<T>::get();
if let Some((index, _)) = Self::get_pool_item(&jurors, prev_juror_info.stake) {
if let Some((index, _)) = Self::get_pool_item(&jurors, &juror) {
jurors.remove(index);
<JurorPool<T>>::put(jurors);
}
Expand Down Expand Up @@ -904,18 +898,9 @@ mod pallet {
);
T::Currency::resolve_creating(&reward_pot, imbalance);

if let Some(prev_juror_info) = <Jurors<T>>::get(ai) {
if let Some((index, _)) = Self::get_pool_item(&jurors, prev_juror_info.stake) {
jurors.remove(index);
}
} else {
log::warn!(
"Juror {:?} not found in Jurors storage for vote aggregation. Market id \
{:?}.",
ai,
market_id
);
debug_assert!(false);
debug_assert!(<Jurors<T>>::contains_key(ai), "Juror {:?} not found.", ai);
if let Some((index, _)) = Self::get_pool_item(&jurors, ai) {
jurors.remove(index);
}
};

Expand Down Expand Up @@ -1114,11 +1099,11 @@ mod pallet {

// Returns (index, pool_item) if the stake associated with the juror was found.
// It returns None otherwise.
pub(crate) fn get_pool_item(
jurors: &[JurorPoolItemOf<T>],
stake: BalanceOf<T>,
) -> Option<(usize, &JurorPoolItemOf<T>)> {
if let Ok(i) = jurors.binary_search_by_key(&stake, |pool_item| pool_item.stake) {
pub(crate) fn get_pool_item<'a, 'b>(
jurors: &'a [JurorPoolItemOf<T>],
juror: &'b T::AccountId,
) -> Option<(usize, &'a JurorPoolItemOf<T>)> {
if let Ok(i) = jurors.binary_search_by_key(juror, |pool_item| pool_item.juror.clone()) {
return Some((i, &jurors[i]));
}
// this None case can happen when the lowest bonded juror was removed (`join_court`)
Expand Down Expand Up @@ -1214,13 +1199,13 @@ mod pallet {
juror
);

if let Some(juror_info) = <Jurors<T>>::get(juror) {
if let Some((index, _)) = Self::get_pool_item(&jurors, juror_info.stake) {
jurors[index].slashed = jurors[index].slashed.saturating_add(slash);
}
} else {
log::warn!("Juror {:?} not found in Jurors storage.", juror);
debug_assert!(false);
debug_assert!(
<Jurors<T>>::contains_key(juror),
"Juror {:?} not found in Jurors storage.",
juror
);
if let Some((index, _)) = Self::get_pool_item(&jurors, juror) {
jurors[index].slashed = jurors[index].slashed.saturating_add(slash);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions zrml/court/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
Balances, Court, ExtBuilder, MarketCommons, Origin, RandomnessCollectiveFlip, Runtime,
System, ALICE, BOB, CHARLIE, DAVE, EVE, FERDIE, GINA, HARRY, IAN, INITIAL_BALANCE,
},
Error, JurorInfo, JurorPoolItem, Jurors, MarketOf,
Error, JurorPoolItem, Jurors, MarketOf,
};
use frame_support::{assert_noop, assert_ok, traits::Hooks};
use pallet_balances::BalanceLock;
Expand Down Expand Up @@ -101,7 +101,7 @@ fn join_court_successfully_stores_a_juror() {
ExtBuilder::default().build().execute_with(|| {
let amount = 2 * BASE;
assert_ok!(Court::join_court(Origin::signed(ALICE), amount));
assert_eq!(Jurors::<Runtime>::iter().next().unwrap(), (ALICE, JurorInfo { stake: amount }));
assert_eq!(Jurors::<Runtime>::iter().next().unwrap(), (ALICE, ()));
});
}

Expand Down
21 changes: 2 additions & 19 deletions zrml/court/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,6 @@

use zeitgeist_primitives::types::OutcomeReport;

/// The general information about a particular juror.
#[derive(
parity_scale_codec::Decode,
parity_scale_codec::Encode,
parity_scale_codec::MaxEncodedLen,
scale_info::TypeInfo,
Clone,
Debug,
PartialEq,
Eq,
)]
pub struct JurorInfo<Balance> {
/// The juror's amount in the stake weighted pool.
/// This amount is used to find a juror with a binary search on the pool.
pub(crate) stake: Balance,
}

/// The information required to finish exiting the court as a juror.
#[derive(
parity_scale_codec::Decode,
Expand Down Expand Up @@ -217,10 +200,10 @@ pub struct Draw<AccountId, Balance, Hash> {
)]
pub struct JurorPoolItem<AccountId, Balance> {
/// The key for the binary search to efficiently find the juror.
/// It has to be a unqiue key for each juror.
pub(crate) stake: Balance,
/// The account which is the juror.
pub(crate) juror: AccountId,
/// The stake of the juror.
pub(crate) stake: Balance,
/// The actual slashed amount. This is useful to reduce the probability
/// of a juror to be selected again.
pub(crate) slashed: Balance,
Expand Down

0 comments on commit c04d5ff

Please sign in to comment.