Skip to content

Commit

Permalink
Reputables can endorse (#268)
Browse files Browse the repository at this point in the history
* introduce endorsement_tickets_per_reputable

* implement has_reputation

* implement is_endorsed

* allow endorsements for reputables

* add benchmarks

* fix benchmarking and add weights

* fix typo

* simplify endorsement code

* add comment in benchmark

* undo benchmarking fixes

* optimize loop order
  • Loading branch information
pifragile authored Sep 19, 2022
1 parent be25a5c commit 615c292
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 71 deletions.
1 change: 0 additions & 1 deletion bazaar/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

use crate as dut;
use frame_support::pallet_prelude::GenesisBuild;

use test_utils::*;

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
Expand Down
17 changes: 13 additions & 4 deletions ceremonies/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,12 +340,16 @@ benchmarks! {

endorse_newcomer {
let cid = create_community::<T>();
let alice: T::AccountId = account("alice", 1, 1);
let cindex = encointer_scheduler::Pallet::<T>::current_ceremony_index();

// we let the newbie be endorsed by a reputable as this is the worst case scenario
let zoran = account_id::<T>(&generate_pair());
Pallet::<T>::fake_reputation((cid, cindex - 1), &zoran, Reputation::VerifiedUnlinked);

// issue some income such that newbies are allowed to register
assert_ok!(encointer_balances::Pallet::<T>::issue(
cid,
&alice,
&zoran,
NominalIncome::from_num(1)
));

Expand All @@ -355,10 +359,9 @@ benchmarks! {
cid, None
));

let cindex = encointer_scheduler::Pallet::<T>::current_ceremony_index();

assert_eq!(<EndorseesCount<T>>::get((cid, cindex)), 0);
}: _(RawOrigin::Signed(alice), cid, account_id::<T>(&newbie))
}: _(RawOrigin::Signed(zoran), cid, account_id::<T>(&newbie))
verify {
assert_eq!(<EndorseesCount<T>>::get((cid, cindex)), 1);
}
Expand Down Expand Up @@ -418,6 +421,12 @@ benchmarks! {
assert_eq!(EndorsementTicketsPerBootstrapper::<T>::get(), 10)
}

set_endorsement_tickets_per_reputable {
}: _(RawOrigin::Root, 10)
verify {
assert_eq!(EndorsementTicketsPerReputable::<T>::get(), 10)
}

set_time_tolerance {
let tolerance: T::Moment = 600_000u32.into();
}: _(RawOrigin::Root, tolerance)
Expand Down
122 changes: 102 additions & 20 deletions ceremonies/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,29 +486,42 @@ pub mod pallet {
Error::<T>::InexistentCommunity
);

ensure!(
<encointer_communities::Pallet<T>>::bootstrappers(&cid).contains(&sender),
Error::<T>::AuthorizationRequired
);

ensure!(
<BurnedBootstrapperNewbieTickets<T>>::get(&cid, &sender) <
Self::endorsement_tickets_per_bootstrapper(),
Error::<T>::NoMoreNewbieTickets
);

let mut cindex = <encointer_scheduler::Pallet<T>>::current_ceremony_index();
if <encointer_scheduler::Pallet<T>>::current_phase() != CeremonyPhaseType::Registering {
cindex += 1; //safe; cindex comes from within, will not overflow at +1/d
}

ensure!(
!<Endorsees<T>>::contains_key((cid, cindex), &newbie),
Self::is_endorsed(&newbie, &(cid, cindex)).is_none(),
Error::<T>::AlreadyEndorsed
);

<BurnedBootstrapperNewbieTickets<T>>::mutate(&cid, sender.clone(), |b| *b += 1); // safe; limited by AMOUNT_NEWBIE_TICKETS
if <encointer_communities::Pallet<T>>::bootstrappers(&cid).contains(&sender) {
ensure!(
<BurnedBootstrapperNewbieTickets<T>>::get(&cid, &sender) <
Self::endorsement_tickets_per_bootstrapper(),
Error::<T>::NoMoreNewbieTickets
);
<BurnedBootstrapperNewbieTickets<T>>::mutate(&cid, sender.clone(), |b| *b += 1);
// safe; limited by AMOUNT_NEWBIE_TICKETS
} else if Self::has_reputation(&sender, &cid) {
ensure!(
<BurnedReputableNewbieTickets<T>>::get(&(cid, cindex), &sender) <
Self::endorsement_tickets_per_reputable(),
Error::<T>::NoMoreNewbieTickets
);
<BurnedReputableNewbieTickets<T>>::mutate(&(cid, cindex), sender.clone(), |b| {
*b += 1
}); // safe; limited by AMOUNT_NEWBIE_TICKETS
} else {
return Err(Error::<T>::AuthorizationRequired.into())
}

<Endorsees<T>>::insert((cid, cindex), newbie.clone(), ());
<EndorseesCount<T>>::mutate((cid, cindex), |c| *c += 1); // safe; limited by AMOUNT_NEWBIE_TICKETS
let new_endorsee_count = Self::endorsee_count((cid, cindex))
.checked_add(1)
.ok_or(<Error<T>>::RegistryOverflow)?;
<EndorseesCount<T>>::insert((cid, cindex), new_endorsee_count);

if <NewbieIndex<T>>::contains_key((cid, cindex), &newbie) {
Self::remove_participant_from_registry(cid, cindex, &newbie)?;
Expand Down Expand Up @@ -660,7 +673,7 @@ pub mod pallet {
#[pallet::weight((<T as Config>::WeightInfo::set_endorsement_tickets_per_bootstrapper(), DispatchClass::Normal, Pays::Yes))]
pub fn set_endorsement_tickets_per_bootstrapper(
origin: OriginFor<T>,
endorsement_tickets_per_bootstrapper: EndorsementTicketsPerBootstrapperType,
endorsement_tickets_per_bootstrapper: EndorsementTicketsType,
) -> DispatchResultWithPostInfo {
<T as pallet::Config>::CeremonyMaster::ensure_origin(origin)?;
<EndorsementTicketsPerBootstrapper<T>>::put(endorsement_tickets_per_bootstrapper);
Expand All @@ -675,6 +688,23 @@ pub mod pallet {
Ok(().into())
}

#[pallet::weight((<T as Config>::WeightInfo::set_endorsement_tickets_per_reputable(), DispatchClass::Normal, Pays::Yes))]
pub fn set_endorsement_tickets_per_reputable(
origin: OriginFor<T>,
endorsement_tickets_per_reputable: EndorsementTicketsType,
) -> DispatchResultWithPostInfo {
<T as pallet::Config>::CeremonyMaster::ensure_origin(origin)?;
<EndorsementTicketsPerReputable<T>>::put(endorsement_tickets_per_reputable);
info!(
target: LOG,
"set endorsement tickets per reputable to {}", endorsement_tickets_per_reputable
);
Self::deposit_event(Event::EndorsementTicketsPerReputableUpdated(
endorsement_tickets_per_reputable,
));
Ok(().into())
}

#[pallet::weight((<T as Config>::WeightInfo::set_reputation_lifetime(), DispatchClass::Normal, Pays::Yes))]
pub fn set_reputation_lifetime(
origin: OriginFor<T>,
Expand Down Expand Up @@ -759,7 +789,9 @@ pub mod pallet {
/// inactivity timeout has changed. affects how many ceremony cycles a community can be idle before getting purged
InactivityTimeoutUpdated(InactivityTimeoutType),
/// The number of endorsement tickets which bootstrappers can give out has changed
EndorsementTicketsPerBootstrapperUpdated(EndorsementTicketsPerBootstrapperType),
EndorsementTicketsPerBootstrapperUpdated(EndorsementTicketsType),
/// The number of endorsement tickets which bootstrappers can give out has changed
EndorsementTicketsPerReputableUpdated(EndorsementTicketsType),
/// reputation lifetime has changed. After this many ceremony cycles, reputations is outdated
ReputationLifetimeUpdated(ReputationLifetimeType),
/// meetup time offset has changed. affects the exact time the upcoming ceremony meetups will take place
Expand Down Expand Up @@ -877,6 +909,18 @@ pub mod pallet {
ValueQuery,
>;

#[pallet::storage]
#[pallet::getter(fn reputable_newbie_tickets)]
pub(super) type BurnedReputableNewbieTickets<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
CommunityCeremony,
Blake2_128Concat,
T::AccountId,
u8,
ValueQuery,
>;

#[pallet::storage]
#[pallet::getter(fn bootstrapper_registry)]
pub(super) type BootstrapperRegistry<T: Config> = StorageDoubleMap<
Expand Down Expand Up @@ -1114,11 +1158,17 @@ pub mod pallet {
pub(super) type InactivityTimeout<T: Config> =
StorageValue<_, InactivityTimeoutType, ValueQuery>;

/// The number newbies a bootstrapper can endorse to accelerate community growth
/// The number of newbies a bootstrapper can endorse to accelerate community growth
#[pallet::storage]
#[pallet::getter(fn endorsement_tickets_per_bootstrapper)]
pub(super) type EndorsementTicketsPerBootstrapper<T: Config> =
StorageValue<_, EndorsementTicketsPerBootstrapperType, ValueQuery>;
StorageValue<_, EndorsementTicketsType, ValueQuery>;

/// The number of newbies a reputable can endorse per cycle to accelerate community growth
#[pallet::storage]
#[pallet::getter(fn endorsement_tickets_per_reputable)]
pub(super) type EndorsementTicketsPerReputable<T: Config> =
StorageValue<_, EndorsementTicketsType, ValueQuery>;

/// The number of ceremony cycles that a participant's reputation is valid for
#[pallet::storage]
Expand All @@ -1140,7 +1190,8 @@ pub mod pallet {
pub location_tolerance: u32,
pub time_tolerance: T::Moment,
pub inactivity_timeout: InactivityTimeoutType,
pub endorsement_tickets_per_bootstrapper: EndorsementTicketsPerBootstrapperType,
pub endorsement_tickets_per_bootstrapper: EndorsementTicketsType,
pub endorsement_tickets_per_reputable: EndorsementTicketsType,
pub reputation_lifetime: ReputationLifetimeType,
pub meetup_time_offset: MeetupTimeOffsetType,
}
Expand All @@ -1157,6 +1208,7 @@ pub mod pallet {
time_tolerance: Default::default(),
inactivity_timeout: Default::default(),
endorsement_tickets_per_bootstrapper: Default::default(),
endorsement_tickets_per_reputable: Default::default(),
reputation_lifetime: Default::default(),
meetup_time_offset: Default::default(),
}
Expand All @@ -1174,6 +1226,7 @@ pub mod pallet {
<TimeTolerance<T>>::put(&self.time_tolerance);
<InactivityTimeout<T>>::put(&self.inactivity_timeout);
<EndorsementTicketsPerBootstrapper<T>>::put(&self.endorsement_tickets_per_bootstrapper);
<EndorsementTicketsPerReputable<T>>::put(&self.endorsement_tickets_per_reputable);
<ReputationLifetime<T>>::put(&self.reputation_lifetime);
<MeetupTimeOffset<T>>::put(&self.meetup_time_offset);
}
Expand Down Expand Up @@ -1281,10 +1334,11 @@ impl<T: Config> Pallet<T> {
<ReputableIndex<T>>::insert((cid, cindex), &sender, &participant_index);
<ReputableCount<T>>::insert((cid, cindex), participant_index);
ParticipantType::Reputable
} else if <Endorsees<T>>::contains_key((cid, cindex), &sender) {
} else if let Some(endorsed_cindex) = Self::is_endorsed(sender, &(cid, cindex)) {
let participant_index = <EndorseeCount<T>>::get((cid, cindex))
.checked_add(1)
.ok_or(Error::<T>::RegistryOverflow)?;
<Endorsees<T>>::remove((cid, endorsed_cindex), &sender);
<EndorseeRegistry<T>>::insert((cid, cindex), &participant_index, &sender);
<EndorseeIndex<T>>::insert((cid, cindex), &sender, &participant_index);
<EndorseeCount<T>>::insert((cid, cindex), participant_index);
Expand Down Expand Up @@ -1946,6 +2000,34 @@ impl<T: Config> Pallet<T> {
));
Ok(())
}

fn has_reputation(participant: &T::AccountId, cid: &CommunityIdentifier) -> bool {
let reputation_lifetime = Self::reputation_lifetime();
let cindex = <encointer_scheduler::Pallet<T>>::current_ceremony_index();
for i in 0..=reputation_lifetime {
if Self::participant_reputation(&(*cid, cindex.saturating_sub(i)), participant)
.is_verified()
{
return true
}
}
false
}

fn is_endorsed(
participant: &T::AccountId,
cc: &CommunityCeremony,
) -> Option<CeremonyIndexType> {
let reputation_lifetime = Self::reputation_lifetime();
for i in 0..=reputation_lifetime {
let cindex = cc.1.saturating_sub(i);
if <Endorsees<T>>::contains_key(&(cc.0, cindex), participant) {
return Some(cindex)
}
}
None
}

#[cfg(any(test, feature = "runtime-benchmarks"))]
// only to be used by tests
fn fake_reputation(cidcindex: CommunityCeremony, account: &T::AccountId, rep: Reputation) {
Expand Down
2 changes: 1 addition & 1 deletion ceremonies/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

pub use crate as dut;
use frame_support::{pallet_prelude::GenesisBuild, parameter_types};

use test_utils::*;

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
Expand Down Expand Up @@ -99,6 +98,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
time_tolerance: TIME_TOLERANCE, // [ms]
inactivity_timeout: 3,
endorsement_tickets_per_bootstrapper: 50,
endorsement_tickets_per_reputable: 2,
reputation_lifetime: 6,
meetup_time_offset: 0,
}
Expand Down
Loading

0 comments on commit 615c292

Please sign in to comment.