Skip to content
Snippets Groups Projects
Commit 1f16c5e0 authored by Cédric Moreau's avatar Cédric Moreau
Browse files

feat(smith-members): smith account can be excluded, not deleted

parent 04e0face
No related branches found
No related tags found
No related merge requests found
......@@ -56,6 +56,8 @@ pub enum SmithStatus {
Pending,
/// The identity has reached the requirements to become a smith and can now goGoOnline() or invite/certify other smiths
Smith,
/// The identity has been removed from the smiths set but is kept to keep track of its certifications
Excluded,
}
#[frame_support::pallet]
......@@ -148,8 +150,7 @@ pub mod pallet {
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
CurrentSession::<T>::put(0);
let mut cert_meta_by_issuer =
BTreeMap::<T::IdtyIndex, SmithCertMeta<T::BlockNumber>>::new();
let mut cert_meta_by_issuer = BTreeMap::<T::IdtyIndex, Vec<T::IdtyIndex>>::new();
for (receiver, issuers) in &self.certs_by_receiver {
// Forbid self-cert
assert!(
......@@ -157,18 +158,13 @@ pub mod pallet {
"Identity cannot certify it-self."
);
// We should insert cert_meta for receivers that have not issued any cert.
cert_meta_by_issuer
.entry(*receiver)
.or_insert(SmithCertMeta {
issued_count: 0,
next_issuable_on: sp_runtime::traits::Zero::zero(),
received_count: 0,
})
.received_count = issuers.len() as u32;
let mut issuers_: Vec<_> = Vec::with_capacity(issuers.len());
for (issuer, maybe_removable_on) in issuers {
// Count issued certs
cert_meta_by_issuer
.entry(*issuer)
.or_insert(vec![])
.push(*receiver);
issuers_.push(*issuer);
}
......@@ -187,6 +183,7 @@ pub mod pallet {
expires_on: Some(
CurrentSession::<T>::get() + T::InactivityMaxDuration::get(),
),
issued_certs: vec![],
received_certs: issuers_,
},
);
......@@ -195,6 +192,16 @@ pub mod pallet {
receiver,
);
}
for (issuer, issued_certs) in cert_meta_by_issuer {
// Write CertsByIssuer
Smiths::<T>::mutate(issuer, |maybe_smith_meta| {
let maybe_smith_meta = maybe_smith_meta
.as_mut()
.expect("issuers must have received certs as well");
maybe_smith_meta.issued_certs = issued_certs;
});
}
}
}
......@@ -227,7 +234,7 @@ pub mod pallet {
/// Invitation must not have been accepted yet
AlreadyAcceptedInvitation,
/// Recevier must not be known among smiths
ReceveirAlreadyHasSmithStatus,
OnlyExcludedCanComeBack,
/// Recevier must have accepted to eventually become a smith
ReceveirMustAcceptInvitation,
/// Issuer must be a smith
......@@ -292,24 +299,37 @@ impl<T: Config> Pallet<T> {
issuer_status == SmithStatus::Smith,
Error::<T>::CannotInvite
);
if let Some(receiver_meta) = Smiths::<T>::get(receiver) {
ensure!(
Smiths::<T>::get(receiver).is_none(),
Error::<T>::ReceveirAlreadyHasSmithStatus
receiver_meta.status == SmithStatus::Excluded,
Error::<T>::OnlyExcludedCanComeBack
);
}
Ok(().into())
}
fn do_invite_smith(receiver: T::IdtyIndex) {
let new_expires_on = CurrentSession::<T>::get() + T::InactivityMaxDuration::get();
// TODO: another way to write this?
if Smiths::<T>::get(receiver).is_some() {
Smiths::<T>::mutate(receiver, |maybe_smith_meta| {
let maybe_smith_meta = maybe_smith_meta.as_mut().expect("checked earlier");
maybe_smith_meta.status = SmithStatus::Invited;
maybe_smith_meta.expires_on = Some(new_expires_on);
maybe_smith_meta.received_certs = vec![];
});
} else {
Smiths::<T>::insert(
receiver,
SmithMeta {
status: SmithStatus::Invited,
expires_on: Some(new_expires_on),
issued_certs: vec![],
received_certs: vec![],
},
);
}
ExpiresOn::<T>::append(new_expires_on, receiver);
}
......@@ -356,8 +376,13 @@ impl<T: Config> Pallet<T> {
}
fn do_certify_smith(receiver: T::IdtyIndex, issuer: T::IdtyIndex) {
Smiths::<T>::mutate(issuer, |maybe_smith_meta| {
let maybe_smith_meta = maybe_smith_meta.as_mut().expect("issuer checked earlier");
maybe_smith_meta.issued_certs.push(receiver);
maybe_smith_meta.issued_certs.sort();
});
Smiths::<T>::mutate(receiver, |maybe_smith_meta| {
let maybe_smith_meta = maybe_smith_meta.as_mut().expect("status checked earlier");
let maybe_smith_meta = maybe_smith_meta.as_mut().expect("receiver checked earlier");
maybe_smith_meta.received_certs.push(issuer);
maybe_smith_meta.received_certs.sort();
// TODO: "as u32" allowed?
......@@ -381,11 +406,36 @@ impl<T: Config> Pallet<T> {
if let Some(smith_meta) = Smiths::<T>::get(smith) {
if let Some(expires_on) = smith_meta.expires_on {
if expires_on == at {
Smiths::<T>::remove(smith);
let mut lost_certs = vec![];
Smiths::<T>::mutate(smith, |maybe_smith_meta| {
let maybe_smith_meta =
maybe_smith_meta.as_mut().expect("checked earlier");
maybe_smith_meta.expires_on = None;
maybe_smith_meta.status = SmithStatus::Excluded;
for cert in &maybe_smith_meta.received_certs {
lost_certs.push(cert.clone());
}
maybe_smith_meta.received_certs = vec![];
// N.B.: the issued certs are kept in case the smith joins back
});
// We remove the lost certs from their issuer's stock
for lost_cert in lost_certs {
Smiths::<T>::mutate(lost_cert, |maybe_smith_meta| {
let maybe_smith_meta =
maybe_smith_meta.as_mut().expect("checked earlier");
if let Ok(index) =
maybe_smith_meta.issued_certs.binary_search(&smith)
{
maybe_smith_meta.issued_certs.remove(index);
}
});
}
// Deletion done
T::OnSmithDelete::on_smith_delete(
smith,
SmithRemovalReason::OfflineTooLong,
);
// TODO: add event? (+ add others too)
}
}
}
......
......@@ -34,10 +34,11 @@ fn process_to_become_a_smith_and_lose_it() {
4 => None,
],
2 => btreemap![
3 => None,
4 => None,
4 => None,
5 => None,
]
],
3 => btreemap![],
4 => btreemap![],
],
})
.execute_with(|| {
......@@ -55,6 +56,7 @@ fn process_to_become_a_smith_and_lose_it() {
SmithMeta {
status: SmithStatus::Pending,
expires_on: Some(5),
issued_certs: vec![],
received_certs: vec![],
}
);
......@@ -68,6 +70,7 @@ fn process_to_become_a_smith_and_lose_it() {
SmithMeta {
status: SmithStatus::Pending,
expires_on: Some(5),
issued_certs: vec![],
received_certs: vec![1],
}
);
......@@ -81,6 +84,7 @@ fn process_to_become_a_smith_and_lose_it() {
SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(5),
issued_certs: vec![],
received_certs: vec![1, 2],
}
);
......@@ -92,9 +96,33 @@ fn process_to_become_a_smith_and_lose_it() {
assert_eq!(Smiths::<Runtime>::get(5).is_some(), true);
// On session 5 no more smiths because of lack of activity
Pallet::<Runtime>::on_new_session(5);
assert_eq!(Smiths::<Runtime>::get(1), None); // TODO: noautoremove (remaining certs)
assert_eq!(Smiths::<Runtime>::get(2), None); // TODO: noautoremove (remaining certs)
assert_eq!(Smiths::<Runtime>::get(5), None); // TODO: noautoremove (remaining certs)
assert_eq!(
Smiths::<Runtime>::get(1),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
assert_eq!(
Smiths::<Runtime>::get(5),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
});
}
......@@ -127,6 +155,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(5),
issued_certs: vec![4],
received_certs: vec![2, 3, 4],
}
);
......@@ -135,6 +164,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(5),
issued_certs: vec![1, 4],
received_certs: vec![3, 4],
}
);
......@@ -143,6 +173,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Pending,
expires_on: Some(5),
issued_certs: vec![1, 2],
received_certs: vec![4],
}
);
......@@ -151,6 +182,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(5),
issued_certs: vec![1, 2, 3],
received_certs: vec![1, 2],
}
);
......@@ -179,6 +211,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Pending,
expires_on: Some(5),
issued_certs: vec![1, 2],
received_certs: vec![4],
}
);
......@@ -193,6 +226,7 @@ fn should_have_checks_on_certify() {
SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(5),
issued_certs: vec![1, 2],
received_certs: vec![1, 4],
}
);
......@@ -211,7 +245,9 @@ fn smith_activity_postpones_expiration() {
2 => btreemap![
3 => None,
4 => None,
]
],
3 => btreemap![],
4 => btreemap![]
],
})
.execute_with(|| {
......@@ -223,14 +259,24 @@ fn smith_activity_postpones_expiration() {
// Smith #2 is online but not #1
Pallet::<Runtime>::smith_goes_online(2);
// On session 5 no more smiths because of lack of activity
// On session 5: exclusion for lack of activity
Pallet::<Runtime>::on_new_session(5);
assert_eq!(Smiths::<Runtime>::get(1), None);
assert_eq!(
Smiths::<Runtime>::get(1),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
// issued_certs is empty because #1 was excluded
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Smith,
expires_on: None,
issued_certs: vec![],
received_certs: vec![3, 4],
})
);
......@@ -238,29 +284,108 @@ fn smith_activity_postpones_expiration() {
// Smith #2 goes offline
Pallet::<Runtime>::on_new_session(6);
Pallet::<Runtime>::smith_goes_offline(2);
assert_eq!(Smiths::<Runtime>::get(1), None);
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(11),
issued_certs: vec![],
received_certs: vec![3, 4],
})
);
// Still not expired on session 10
Pallet::<Runtime>::on_new_session(10);
assert_eq!(Smiths::<Runtime>::get(1), None);
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(11),
issued_certs: vec![],
received_certs: vec![3, 4],
})
);
// Finally expired on session 11
// But expired on session 11
Pallet::<Runtime>::on_new_session(11);
assert_eq!(Smiths::<Runtime>::get(1), None);
assert_eq!(Smiths::<Runtime>::get(2), None);
assert_eq!(
Smiths::<Runtime>::get(1),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![],
received_certs: vec![]
})
);
});
}
#[test]
fn smith_coming_back_recovers_its_issued_certs() {
new_test_ext(GenesisConfig {
certs_by_receiver: btreemap![
1 => btreemap![
2 => None,
3 => None,
4 => None,
],
2 => btreemap![
3 => None,
4 => None,
],
3 => btreemap![
4 => None,
1 => None,
],
4 => btreemap![]
],
})
.execute_with(|| {
// Not activity for Smith #2
Pallet::<Runtime>::smith_goes_online(1);
Pallet::<Runtime>::smith_goes_online(3);
Pallet::<Runtime>::smith_goes_online(4);
// Smith #2 gets excluded
Pallet::<Runtime>::on_new_session(5);
// The issued certs are preserved
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Excluded,
expires_on: None,
issued_certs: vec![1],
received_certs: vec![]
})
);
// Smith #2 comes back
assert_ok!(Pallet::<Runtime>::invite_smith(RuntimeOrigin::signed(1), 2));
assert_ok!(Pallet::<Runtime>::accept_invitation(RuntimeOrigin::signed(
2
)));
assert_ok!(Pallet::<Runtime>::certify_smith(
RuntimeOrigin::signed(1),
2
));
assert_ok!(Pallet::<Runtime>::certify_smith(
RuntimeOrigin::signed(3),
2
));
// Smith #2 is back with its issued certs recovered, but not its received certs
assert_eq!(
Smiths::<Runtime>::get(2),
Some(SmithMeta {
status: SmithStatus::Smith,
expires_on: Some(10),
issued_certs: vec![1],
received_certs: vec![1, 3]
})
);
});
}
......@@ -30,7 +30,9 @@ pub struct SmithMeta<IdtyIndex> {
pub status: SmithStatus,
/// the session at which the smith will expire (for lack of validation activity)
pub expires_on: Option<SessionIndex>,
/// the recertifications received from other smiths
/// the certifications issued to other smiths
pub issued_certs: Vec<IdtyIndex>,
/// the certifications received from other smiths
pub received_certs: Vec<IdtyIndex>,
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment