Skip to content
Snippets Groups Projects

automatically claim membership

Merged Hugo Trentesaux requested to merge hugo-dev into master
2 files
+ 162
105
Compare changes
  • Side-by-side
  • Inline

Files

+ 154
95
@@ -37,6 +37,7 @@ use pallet_authority_members::SessionIndex;
use sp_distance::{InherentError, INHERENT_IDENTIFIER};
use sp_inherents::{InherentData, InherentIdentifier};
use sp_std::convert::TryInto;
use sp_std::prelude::*;
type IdtyIndex = u32;
@@ -77,6 +78,8 @@ pub mod pallet {
#[pallet::constant]
type MinAccessibleReferees: Get<Perbill>;
/// Number of session to keep a positive evaluation result
// (antispam mechanism)
#[pallet::constant]
type ResultExpiration: Get<u32>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
@@ -127,18 +130,34 @@ pub mod pallet {
pub type EvaluationBlock<T: Config> =
StorageValue<_, <T as frame_system::Config>::Hash, ValueQuery>;
/// Distance evaluation status by identity
/// Pending evaluation requests
///
/// * `.0` is the account who requested an evaluation and reserved the price,
/// account who requested an evaluation and reserved the price,
/// for whom the price will be unreserved or slashed when the evaluation completes.
/// * `.1` is the status of the evaluation.
#[pallet::storage]
#[pallet::getter(fn identity_distance_status)]
pub type IdentityDistanceStatus<T: Config> = StorageMap<
#[pallet::getter(fn pending_evaluation_request)]
pub type PendingEvaluationRequest<T: Config> = StorageMap<
_,
Twox64Concat,
<T as pallet_identity::Config>::IdtyIndex,
(<T as frame_system::Config>::AccountId, DistanceStatus),
<T as frame_system::Config>::AccountId,
OptionQuery,
>;
/// Valid evaluation results
#[pallet::storage]
#[pallet::getter(fn valid_evaluation_result)]
pub type ValidEvaluationResult<T: Config> =
StorageMap<_, Twox64Concat, <T as pallet_identity::Config>::IdtyIndex, (), OptionQuery>;
/// Valid evaluation expiry blocks
#[pallet::storage]
#[pallet::getter(fn valid_evaluation_expire_on)]
pub type ValidEvaluationExpireOn<T: Config> = StorageMap<
_,
Twox64Concat,
u32,
Vec<<T as pallet_identity::Config>::IdtyIndex>,
OptionQuery,
>;
@@ -161,9 +180,9 @@ pub mod pallet {
idty_index: T::IdtyIndex,
who: T::AccountId,
},
/// A distance evaluation was updated.
/// A distance evaluation was updated. // TODO refac
EvaluationUpdated { evaluator: T::AccountId },
/// A distance status was forced.
/// A distance status was forced. // TODO check if necessary to differenciate
EvaluationStatusForced {
idty_index: T::IdtyIndex,
status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
@@ -183,7 +202,13 @@ pub mod pallet {
/// No author for this block.
NoAuthor,
/// Caller has no identity.
NoIdentity,
CallerHasNoIdentity,
/// Caller identity not found.
CallerIdentityNotFound,
/// Caller not member.
CallerNotMember,
/// Target identity not found.
TargetIdentityNotFound,
/// Evaluation queue is full.
QueueFull,
/// Too many evaluators in the current evaluation pool.
@@ -191,7 +216,10 @@ pub mod pallet {
/// Evaluation result has a wrong length.
WrongResultLength,
/// Targeted distance evaluation request is only possible for an unvalidated identity
DistanceRequestNotAllowed,
DistanceRequestOnlyAllowedForUnvalidated,
/// Can not request distance evaluation when a valid result has already been published recently
// (antispam)
ValidDistanceResultAlreadyAvailable,
}
#[pallet::hooks]
@@ -223,18 +251,9 @@ pub mod pallet {
pub fn request_distance_evaluation(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let idty =
pallet_identity::IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?;
let idty = Self::check_request_distance_evaluation_self(&who)?;
// TODO is it necessary to check that the same account performed the request?
// TODO what if the distance status is existing but valid?
ensure!(
IdentityDistanceStatus::<T>::get(idty)
!= Some((who.clone(), DistanceStatus::Pending)),
Error::<T>::AlreadyInEvaluation
);
Pallet::<T>::do_request_distance_evaluation(who, idty)?;
Pallet::<T>::do_request_distance_evaluation(&who, idty)?;
Ok(().into())
}
@@ -248,27 +267,9 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
// check that the caller has an identity (TODO is this necessary ?)
// let _ =
// pallet_identity::IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?;
// get the identity value of the target
let target_idty =
pallet_identity::Identities::<T>::get(target).ok_or(Error::<T>::NoIdentity)?;
// check that target is unvalidated
ensure!(
target_idty.status == pallet_identity::IdtyStatus::Unvalidated,
Error::<T>::DistanceRequestNotAllowed
);
// check that no distance status is already there
ensure!(
IdentityDistanceStatus::<T>::get(target).is_none(),
Error::<T>::AlreadyInEvaluation
);
Self::check_request_distance_evaluation_for(&who, target)?;
Pallet::<T>::do_request_distance_evaluation(who, target)?;
Pallet::<T>::do_request_distance_evaluation(&who, target)?;
Ok(().into())
}
@@ -318,18 +319,13 @@ pub mod pallet {
/// * `status.1` is the status of the evaluation.
#[pallet::call_index(3)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::force_set_distance_status())]
pub fn force_set_distance_status(
pub fn force_valid_distance_status(
origin: OriginFor<T>,
identity: <T as pallet_identity::Config>::IdtyIndex,
status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_set_distance_status(identity, status.clone());
Self::deposit_event(Event::EvaluationStatusForced {
idty_index: identity,
status,
});
Self::do_valid_distance_status(identity);
Ok(())
}
}
@@ -398,9 +394,74 @@ pub mod pallet {
}
}
/// check that request distance evaluation is allowed
fn check_request_distance_evaluation_self(
who: &T::AccountId,
) -> Result<<T as pallet_identity::Config>::IdtyIndex, DispatchError> {
// caller has an identity
let idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
// TODO some of the following can be moved to a "check idty call allowed" managed by wot
let idty = pallet_identity::Identities::<T>::get(idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is member
ensure!(
idty.status == pallet_identity::IdtyStatus::NotMember
|| idty.status == pallet_identity::IdtyStatus::Member,
Error::<T>::CallerNotMember
);
Self::check_request_distance_evaluation_common(idty_index)?;
Ok(idty_index)
}
/// check that targeted request distance evaluation is allowed
fn check_request_distance_evaluation_for(
who: &T::AccountId,
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// caller has an identity
let caller_idty_index = pallet_identity::IdentityIndexOf::<T>::get(&who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
// TODO some of the following can be moved to a "check idty call allowed" managed by wot
let caller_idty = pallet_identity::Identities::<T>::get(caller_idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is member
ensure!(
caller_idty.status == pallet_identity::IdtyStatus::Member,
Error::<T>::CallerNotMember
);
// target has an identity
let target_idty = pallet_identity::Identities::<T>::get(target)
.ok_or(Error::<T>::TargetIdentityNotFound)?;
// target is unvalidated
ensure!(
target_idty.status == pallet_identity::IdtyStatus::Unvalidated,
Error::<T>::DistanceRequestOnlyAllowedForUnvalidated
);
Self::check_request_distance_evaluation_common(target)?;
Ok(())
}
// common checks between check_request_distance_evaluation _self and _for
fn check_request_distance_evaluation_common(
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// no pending evaluation request
ensure!(
PendingEvaluationRequest::<T>::get(target).is_none(),
Error::<T>::AlreadyInEvaluation
);
// no valid status (antispam)
ensure!(
ValidEvaluationResult::<T>::get(target).is_none(),
Error::<T>::ValidDistanceResultAlreadyAvailable
);
Ok(())
}
/// request distance evaluation in current pool
fn do_request_distance_evaluation(
who: T::AccountId,
who: &T::AccountId,
idty_index: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
Pallet::<T>::mutate_current_pool(
@@ -411,19 +472,19 @@ pub mod pallet {
Error::<T>::QueueFull
);
T::Currency::reserve(&who, <T as Config>::EvaluationPrice::get())?;
T::Currency::reserve(who, <T as Config>::EvaluationPrice::get())?;
current_pool
.evaluations
.try_push((idty_index, median::MedianAcc::new()))
.map_err(|_| Error::<T>::QueueFull)?;
IdentityDistanceStatus::<T>::insert(
idty_index,
(&who, DistanceStatus::Pending),
);
PendingEvaluationRequest::<T>::insert(idty_index, who);
Self::deposit_event(Event::EvaluationRequested { idty_index, who });
Self::deposit_event(Event::EvaluationRequested {
idty_index,
who: who.clone(),
});
Ok(())
},
)
@@ -466,14 +527,17 @@ pub mod pallet {
}
/// Set the distance status using IdtyIndex and AccountId
pub fn do_set_distance_status(
identity: <T as pallet_identity::Config>::IdtyIndex,
status: Option<(<T as frame_system::Config>::AccountId, DistanceStatus)>,
) {
IdentityDistanceStatus::<T>::set(identity, status.clone());
if let Some((_, DistanceStatus::Valid)) = status {
T::OnValidDistanceStatus::on_valid_distance_status(identity);
}
pub fn do_valid_distance_status(idty: <T as pallet_identity::Config>::IdtyIndex) {
// expiration session index
let expire_on =
pallet_session::Pallet::<T>::current_index() + T::ResultExpiration::get();
// index valid result
ValidEvaluationResult::<T>::set(idty, Some(()));
// schedule expiry
ValidEvaluationExpireOn::<T>::append(expire_on, idty);
// callback
T::OnValidDistanceStatus::on_valid_distance_status(idty);
// TODO event
}
}
@@ -495,42 +559,37 @@ pub mod pallet {
MedianResult::One(m) => m,
MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1)
};
// flag to capture distance status
// distance status criterion
let is_distance_status_valid = median >= T::MinAccessibleReferees::get();
// mutate the distance status accordingly
IdentityDistanceStatus::<T>::mutate(idty, |entry| {
if let Some((account_id, status)) = entry.as_mut() {
// result is ok
if is_distance_status_valid {
// unreserve price
T::Currency::unreserve(
account_id,
<T as Config>::EvaluationPrice::get(),
);
// update distance status
*status = DistanceStatus::Valid;
}
// result is ko
else {
// slash requester
T::Currency::slash_reserved(
account_id,
<T as Config>::EvaluationPrice::get(),
);
// update distance status
*status = DistanceStatus::Invalid;
}
} // each entry should be Some((_, DistanceStatus::Pending))
});
// consequences of valid status
// take requester and perform unreserve or slash
if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) {
if is_distance_status_valid {
T::Currency::unreserve(
&requester,
<T as Config>::EvaluationPrice::get(),
);
} else {
T::Currency::slash_reserved(
&requester,
<T as Config>::EvaluationPrice::get(),
);
}
}
// if evaluation happened without request, it's ok to do nothing
// if evaluation is positive, remember it for antispam
// and perform callbacks
if is_distance_status_valid {
T::OnValidDistanceStatus::on_valid_distance_status(idty);
Self::do_valid_distance_status(idty);
} else {
// TODO event
}
} else if let Some((account_id, _status)) = IdentityDistanceStatus::<T>::take(idty)
{
// when no result is published, all funds are unreserved
}
// when no result is published, all funds are unreserved
else if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) {
<T as Config>::Currency::unreserve(
&account_id,
&requester,
<T as Config>::EvaluationPrice::get(),
);
}
Loading