Skip to content
Snippets Groups Projects
Commit 90a5352c authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist: Committed by Pascal Engélibert
Browse files

fixes & working end2end tests

parent 70b5963c
No related branches found
No related tags found
1 merge request!105Distance Oracle
Showing
with 568 additions and 272 deletions
......@@ -14,5 +14,6 @@
"port_p2p": 19931,
"port_rpc": 19932,
"port_ws": 19933
}
},
"rust-analyzer.showUnlinkedFileNotification": false
}
\ No newline at end of file
......@@ -836,6 +836,15 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "cmake"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130"
dependencies = [
"cc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
......@@ -1558,7 +1567,11 @@ dependencies = [
name = "distance-oracle"
version = "0.1.0"
dependencies = [
"bincode",
"clap 4.1.4",
"dubp-wot",
"flate2",
"num-traits",
"parity-scale-codec",
"rayon",
"sp-core",
......@@ -1596,6 +1609,19 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00704156a7de8df8da0911424e30c2049957b0a714542a44e05fe693dd85313"
[[package]]
name = "dubp-wot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f62bdc1abf2da65794c9fd7fe46f7328834a97a2346048d46b0b6676302de0"
dependencies = [
"log",
"once_cell",
"parking_lot 0.11.2",
"rayon",
"serde",
]
[[package]]
name = "duniter"
version = "0.3.0"
......@@ -1683,6 +1709,7 @@ dependencies = [
"clap 3.2.23",
"ctrlc",
"cucumber",
"distance-oracle",
"env_logger 0.9.3",
"hex",
"notify",
......@@ -4206,6 +4233,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
dependencies = [
"cc",
"cmake",
"libc",
"pkg-config",
"vcpkg",
]
......@@ -9800,7 +9829,7 @@ version = "1.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
"digest 0.10.6",
"rand 0.8.5",
"static_assertions",
......
......@@ -78,12 +78,9 @@ where
),
)?
.map_or_else(Default::default, |raw| {
pallet_distance::EvaluationPool::<
AccountId32,
IdtyIndex,
ConstU32<100>,
ConstU32<100>,
>::decode(&mut &raw.0[..])
pallet_distance::EvaluationPool::<AccountId32, IdtyIndex, ConstU32<100>, 100>::decode(
&mut &raw.0[..],
)
.expect("cannot decode EvaluationPool")
});
......
......@@ -10,6 +10,7 @@ edition = "2021"
sp-distance = { path = "../primitives/distance" }
codec = { package = "parity-scale-codec", version = "3.1.5" }
num-traits = "0.2.15"
rayon = "1.7.0"
sp-core = { git = "https://github.com/duniter/substrate.git", branch = "duniter-substrate-v0.9.32" }
sp-runtime = { git = "https://github.com/duniter/substrate.git", branch = "duniter-substrate-v0.9.32" }
......@@ -22,6 +23,13 @@ tokio = { version = "1.15.0", features = [
"macros",
], optional = true }
[dev-dependencies]
bincode = "1.3.3"
dubp-wot = "0.11.1"
flate2 = { version = "1.0", features = [
"zlib-ng-compat",
], default-features = false }
[features]
default = ["standalone"]
standalone = ["clap", "tokio"]
......
use crate::{
gdev,
gdev::runtime_types::{
pallet_distance::median::MedianAcc,
sp_arithmetic::per_things::Perbill,
sp_core::bounded::{bounded_btree_set::BoundedBTreeSet, bounded_vec::BoundedVec},
},
};
use sp_core::H256;
pub type Client = subxt::OnlineClient<crate::GdevConfig>;
pub type AccountId = subxt::ext::sp_runtime::AccountId32;
pub type IdtyIndex = u32;
pub type EvaluationPool<AccountId, IdtyIndex> = (
BoundedVec<(IdtyIndex, MedianAcc<Perbill>)>,
BoundedBTreeSet<AccountId>,
);
pub async fn client(rpc_url: String) -> Client {
Client::from_url(rpc_url).await.unwrap()
}
pub async fn parent_hash(client: &Client) -> H256 {
client
.storage()
.fetch(&gdev::storage().system().parent_hash(), None)
.await
.unwrap()
.unwrap()
}
pub async fn current_session(client: &Client, parent_hash: H256) -> u32 {
client
.storage()
.fetch(
&gdev::storage().session().current_index(),
Some(parent_hash),
)
.await
.unwrap()
.unwrap_or_default()
}
pub async fn current_pool(
client: &Client,
parent_hash: H256,
current_session: u32,
) -> Option<EvaluationPool<AccountId, IdtyIndex>> {
client
.storage()
.fetch(
&match current_session % 3 {
0 => gdev::storage().distance().evaluation_pool1(),
1 => gdev::storage().distance().evaluation_pool2(),
2 => gdev::storage().distance().evaluation_pool0(),
_ => unreachable!("n%3<3"),
},
Some(parent_hash),
)
.await
.unwrap()
}
pub async fn evaluation_block(client: &Client, parent_hash: H256) -> H256 {
client
.storage()
.fetch(
&gdev::storage().distance().evaluation_block(),
Some(parent_hash),
)
.await
.unwrap()
.unwrap()
}
pub async fn member_iter(
client: &Client,
evaluation_block: H256,
) -> subxt::storage::KeyIter<
crate::GdevConfig,
Client,
subxt::metadata::DecodeStaticType<gdev::runtime_types::sp_membership::MembershipData<u32>>,
> {
client
.storage()
.iter(
gdev::storage().membership().membership(0),
100,
Some(evaluation_block),
)
.await
.unwrap()
}
pub async fn cert_iter(
client: &Client,
evaluation_block: H256,
) -> subxt::storage::KeyIter<
crate::GdevConfig,
Client,
subxt::metadata::DecodeStaticType<Vec<(u32, u32)>>,
> {
client
.storage()
.iter(
gdev::storage().cert().certs_by_receiver(0),
100,
Some(evaluation_block),
)
.await
.unwrap()
}
#[cfg(not(test))]
mod api;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
#[cfg(test)]
use mock as api;
use api::{AccountId, IdtyIndex};
use codec::Encode;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator;
use std::collections::{HashMap, HashSet};
use std::io::Write;
use std::path::PathBuf;
use subxt::ext::sp_runtime::Perbill;
use subxt::storage::StorageKey;
pub struct Settings {
pub evaluation_result_dir: PathBuf,
pub max_depth: u32,
pub rpc_url: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
evaluation_result_dir: PathBuf::from("/tmp/duniter/chains/gdev/distance"),
max_depth: 5,
rpc_url: String::from("ws://127.0.0.1:9944"),
}
}
}
#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
pub mod gdev {}
pub type Client = subxt::OnlineClient<GdevConfig>;
pub enum GdevConfig {}
impl subxt::config::Config for GdevConfig {
type Index = u32;
type BlockNumber = u32;
type Hash = sp_core::H256;
type Hashing = subxt::ext::sp_runtime::traits::BlakeTwo256;
type AccountId = subxt::ext::sp_runtime::AccountId32;
type AccountId = AccountId;
type Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>;
type Header = subxt::ext::sp_runtime::generic::Header<
Self::BlockNumber,
......@@ -61,86 +55,101 @@ impl From<u64> for Tip {
}
}
type IdtyIndex = u32;
pub struct Settings {
pub evaluation_result_dir: PathBuf,
pub max_depth: u32,
pub rpc_url: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
evaluation_result_dir: PathBuf::from("/tmp/duniter/chains/gdev/distance"),
max_depth: 5,
rpc_url: String::from("ws://127.0.0.1:9944"),
}
}
}
pub async fn run(settings: Settings) {
let client = Client::from_url(settings.rpc_url).await.unwrap();
pub async fn run_and_save(settings: Settings) {
let Some((evaluation, current_session, evaluation_result_path)) = run(&settings, true).await else {return};
let parent_hash = client
.storage()
.fetch(&gdev::storage().system().parent_hash(), None)
.await
.unwrap()
let mut evaluation_result_file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(evaluation_result_path)
.unwrap();
let current_session = client
.storage()
.fetch(
&gdev::storage().session().current_index(),
Some(parent_hash),
evaluation_result_file
.write_all(
&sp_distance::ComputationResult {
distances: evaluation,
}
.encode(),
)
.await
.unwrap()
.unwrap_or_default();
.unwrap();
// Remove old results
let mut files_to_remove = Vec::new();
for entry in settings.evaluation_result_dir.read_dir().unwrap().flatten() {
if let Ok(entry_name) = entry.file_name().into_string() {
if let Ok(entry_session) = entry_name.parse::<isize>() {
if current_session as isize - entry_session > 3 {
files_to_remove.push(entry.path());
}
}
}
}
files_to_remove.into_iter().for_each(|f| {
std::fs::remove_file(&f).unwrap_or_else(move |e| {
eprintln!("Warning: Cannot remove old result file `{f:?}`: {e:?}")
});
});
}
/// Returns `(evaluation, current_session, evaluation_result_path)`
pub async fn run(
settings: &Settings,
handle_fs: bool,
) -> Option<(Vec<sp_runtime::Perbill>, u32, PathBuf)> {
let client = api::client(settings.rpc_url.clone()).await;
let parent_hash = api::parent_hash(&client).await;
let current_session = api::current_session(&client, parent_hash).await;
// Fetch the pending identities
let Some(evaluation_pool) = client
.storage()
.fetch(
&match current_session % 3 {
0 => gdev::storage().distance().evaluation_pool1(),
1 => gdev::storage().distance().evaluation_pool2(),
2 => gdev::storage().distance().evaluation_pool0(),
_ => unreachable!("n%3<3"),
},
Some(parent_hash),
)
.await
.unwrap() else {
let Some(evaluation_pool) = api::current_pool(&client, parent_hash, current_session).await
else {
println!("Pool does not exist");
return
return None
};
// Stop if nothing to evaluate
if evaluation_pool.0 .0.is_empty() {
println!("Pool is empty");
return;
return None;
}
let evaluation_result_path = settings
.evaluation_result_dir
.join((current_session + 1).to_string());
if handle_fs {
// Stop if already evaluated
if evaluation_result_path.try_exists().unwrap() {
println!("File already exists");
return;
return None;
}
let evaluation_block = client
.storage()
.fetch(
&gdev::storage().distance().evaluation_block(),
Some(parent_hash),
)
.await
.unwrap()
.unwrap();
std::fs::create_dir_all(&settings.evaluation_result_dir).unwrap();
}
let evaluation_block = api::evaluation_block(&client, parent_hash).await;
// member idty -> issued certs
let mut members = HashMap::<IdtyIndex, u32>::new();
let mut members_iter = client
.storage()
.iter(
gdev::storage().membership().membership(0),
100,
Some(evaluation_block),
)
.await
.unwrap();
let mut members_iter = api::member_iter(&client, evaluation_block).await;
while let Some((member_idty, _membership_expire)) = members_iter.next().await.unwrap() {
members.insert(idty_id_from_storage_key(&member_idty), 0);
}
......@@ -152,15 +161,7 @@ pub async fn run(settings: Settings) {
// idty -> received certs
let mut received_certs = HashMap::<IdtyIndex, Vec<IdtyIndex>>::new();
let mut certs_iter = client
.storage()
.iter(
gdev::storage().cert().certs_by_receiver(0),
100,
Some(evaluation_block),
)
.await
.unwrap();
let mut certs_iter = api::cert_iter(&client, evaluation_block).await;
while let Some((receiver, issuers)) = certs_iter.next().await.unwrap() {
let receiver = idty_id_from_storage_key(&receiver);
// Update members' issued certs count
......@@ -188,48 +189,20 @@ pub async fn run(settings: Settings) {
members.retain(|_idty, issued_certs| *issued_certs >= min_certs_for_referee);
let referees = members;
let evaluation: Vec<Perbill> = evaluation_pool
let evaluation = evaluation_pool
.0
.0
.into_par_iter()
.as_slice()
.par_iter()
.map(|(idty, _)| {
Perbill::from_rational(
distance_rule(&received_certs, &referees, settings.max_depth, idty),
sp_runtime::Perbill::from_rational(
distance_rule(&received_certs, &referees, settings.max_depth, *idty),
referees.len() as u32,
)
})
.collect();
let mut evaluation_result_file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(evaluation_result_path)
.unwrap();
evaluation_result_file
.write_all(
&sp_distance::ComputationResult {
distances: evaluation,
}
.encode(),
)
.unwrap();
// Remove old results
let mut files_to_remove = Vec::new();
for entry in settings.evaluation_result_dir.read_dir().unwrap().flatten() {
if let Ok(entry_name) = entry.file_name().into_string() {
if let Ok(entry_session) = entry_name.parse::<isize>() {
if current_session as isize - entry_session > 3 {
files_to_remove.push(entry.path());
}
}
}
}
files_to_remove.into_iter().for_each(|f| {
std::fs::remove_file(&f).unwrap_or_else(move |e| {
eprintln!("Warning: Cannot remove old result file `{f:?}`: {e:?}")
});
});
Some((evaluation, current_session, evaluation_result_path))
}
fn distance_rule_recursive(
......@@ -276,3 +249,11 @@ fn distance_rule(
fn idty_id_from_storage_key(storage_key: &StorageKey) -> IdtyIndex {
u32::from_le_bytes(storage_key.as_ref()[40..44].try_into().unwrap())
}
/*
impl num_traits::Pow<usize> for Perbill {}
impl std::ops::Div<Perbill> for Perbill {}
impl num_traits::Bounded for Perbill {}
impl Ord for Perbill {}
impl Eq for Perbill {}
impl PerThing for Perbill {}*/
......@@ -15,7 +15,7 @@ struct Cli {
async fn main() {
let cli = Cli::parse();
distance_oracle::run(distance_oracle::Settings {
distance_oracle::run_and_save(distance_oracle::Settings {
evaluation_result_dir: cli.evaluation_result_dir.into(),
max_depth: cli.max_depth,
rpc_url: cli.rpc_url,
......
use crate::gdev::runtime_types::{
pallet_distance::median::MedianAcc, sp_arithmetic::per_things::Perbill,
};
use sp_core::H256;
use std::collections::BTreeSet;
pub struct Client;
pub type AccountId = subxt::ext::sp_runtime::AccountId32;
pub type IdtyIndex = u32;
pub type EvaluationPool<AccountId, IdtyIndex> =
((Vec<(IdtyIndex, MedianAcc<Perbill>)>,), BTreeSet<AccountId>);
pub async fn client(_rpc_url: String) -> Client {
Client
}
pub async fn parent_hash(_client: &Client) -> H256 {
Default::default()
}
pub async fn current_session(_client: &Client, _parent_hash: H256) -> u32 {
0
}
pub async fn current_pool(
_client: &Client,
_parent_hash: H256,
_current_session: u32,
) -> Option<EvaluationPool<AccountId, IdtyIndex>> {
Some(((vec![],), std::collections::BTreeSet::new()))
}
pub async fn evaluation_block(_client: &Client, _parent_hash: H256) -> H256 {
Default::default()
}
pub async fn member_iter(
_client: &Client,
_evaluation_block: H256,
) -> KeyIter<(subxt::storage::StorageKey, ())> {
KeyIter::new(Vec::new())
}
pub async fn cert_iter(
_client: &Client,
_evaluation_block: H256,
) -> KeyIter<(subxt::storage::StorageKey, Vec<(u32, u32)>)> {
KeyIter::new(Vec::new())
}
pub struct KeyIter<T>(std::vec::IntoIter<T>);
impl<T> KeyIter<T> {
fn new(items: Vec<T>) -> Self {
Self(items.into_iter())
}
pub async fn next(&mut self) -> Result<Option<T>, subxt::error::Error>
where
T: Clone,
{
Ok(self.0.next())
}
}
use dubp_wot::data::rusty::RustyWebOfTrust;
use flate2::read::ZlibDecoder;
use std::{fs::File, io::Read};
#[tokio::test]
async fn test_distance_against_v1() {
let _wot = wot_from_v1_file();
crate::run(&Default::default(), false).await;
}
fn wot_from_v1_file() -> RustyWebOfTrust {
let file = File::open("wot.deflate").expect("Cannot open wot.deflate");
let mut decompressor = ZlibDecoder::new(file);
let mut decompressed_bytes = Vec::new();
decompressor
.read_to_end(&mut decompressed_bytes)
.expect("Cannot decompress wot.deflate");
bincode::deserialize::<RustyWebOfTrust>(&decompressed_bytes).expect("Cannot decode wot.deflate")
}
File added
......@@ -14,6 +14,7 @@ async-trait = "0.1"
clap = { version = "3.2.23", features = ["derive"] }
ctrlc = "3.2.2"
cucumber = "0.11"
distance-oracle = { path = "../distance-oracle", default_features = false }
env_logger = "0.9.0"
hex = "0.4"
notify = "4.0"
......
Feature: Distance
Scenario: Alice certifies Eve
When alice sends 6 ĞD to dave
When 15 blocks later
When alice creates identity for dave
Then dave identity should be created
Then dave should be certified by alice
When dave confirms his identity with pseudo "dave"
Then dave identity should be confirmed
When bob certifies dave
Then dave should be certified by bob
When alice requests distance evaluation for dave
Then dave should have distance result in 2 sessions
When 30 blocks later
Then dave should have distance result in 1 session
When distance oracle runs
Then dave should have distance result in 1 session
When 30 blocks later
Then dave should have distance result in 0 session
Then dave should have distance ok
When alice validates dave identity
When 3 blocks later
Then dave identity should be validated
......@@ -5,8 +5,9 @@ Feature: Identity creation
# - account creation fees (3 ĞD)
# - existential deposit (2 ĞD)
# - transaction fees (below 1 ĞD)
When alice sends 6 ĞD to dave
When bob sends 6 ĞD to eve
When alice sends 7 ĞD to dave
When bob sends 750 cĞD to dave
When charlie sends 6 ĞD to eve
# alice last certification is counted from block zero
# then next cert can be done after cert_period, which is 15
When 15 block later
......@@ -20,6 +21,12 @@ Feature: Identity creation
When charlie certifies dave
Then dave should be certified by bob
Then dave should be certified by charlie
When 3 block later
When dave requests distance evaluation
Then dave should have distance result in 2 sessions
When 30 blocks later
Then dave should have distance result in 1 session
When alice runs distance oracle
When 30 blocks later
Then dave should have distance ok
When eve validates dave identity
Then dave identity should be validated
......@@ -19,18 +19,19 @@ use super::gdev::runtime_types::pallet_identity;
use super::*;
use crate::DuniterWorld;
use sp_keyring::AccountKeyring;
use subxt::tx::PairSigner;
use subxt::ext::sp_runtime::AccountId32;
use subxt::tx::{PairSigner, Signer};
pub async fn request_evaluation(client: &Client, from: AccountKeyring, to: u32) -> Result<()> {
let from = PairSigner::new(from.pair());
pub async fn request_evaluation(client: &Client, origin: AccountKeyring) -> Result<()> {
let origin = PairSigner::new(origin.pair());
let _events = create_block_with_extrinsic(
client,
client
.tx()
.create_signed(
&gdev::tx().distance().evaluate_distance(to),
&from,
&gdev::tx().distance().evaluate_distance(),
&origin,
BaseExtrinsicParamsBuilder::new(),
)
.await?,
......@@ -39,3 +40,52 @@ pub async fn request_evaluation(client: &Client, from: AccountKeyring, to: u32)
Ok(())
}
pub async fn run_oracle(client: &Client, origin: AccountKeyring, rpc_url: String) -> Result<()> {
let origin = PairSigner::new(origin.pair());
let account_id: &AccountId32 = origin.account_id();
if let Some((distances, _current_session, _evaluation_result_path)) = distance_oracle::run(
&distance_oracle::Settings {
evaluation_result_dir: PathBuf::default(),
max_depth: 5,
rpc_url,
},
false,
)
.await
{
for _ in 0..30 {
super::create_empty_block(client).await?;
}
let _events = create_block_with_extrinsic(
client,
client
.tx()
.create_signed(
&gdev::tx().sudo().sudo(/*gdev::runtime_types::gdev_runtime::RuntimeCall::UpgradeOrigin(gdev::runtime_types::pallet_upgrade_origin::pallet::Call::dispatch_as_root {
call: Box::new(*/gdev::runtime_types::gdev_runtime::RuntimeCall::Distance(
gdev::runtime_types::pallet_distance::pallet::Call::force_update_evaluation {
evaluator: account_id.clone(),
computation_result:
gdev::runtime_types::sp_distance::ComputationResult {
distances: unsafe {
std::mem::transmute(distances)
},
},
},
)/*)
})*/),
//&gdev::tx().upgrade_origin().dispatch_as_root(
//),
&origin,
BaseExtrinsicParamsBuilder::new(),
)
.await?,
)
.await?;
}
Ok(())
}
......@@ -84,9 +84,11 @@ pub async fn validate_identity(client: &Client, from: AccountKeyring, to: u32) -
&from,
BaseExtrinsicParamsBuilder::new(),
)
.await?,
.await
.unwrap(),
)
.await?;
.await
.unwrap();
Ok(())
}
......
......@@ -322,31 +322,30 @@ async fn confirm_identity(world: &mut DuniterWorld, from: String, pseudo: String
async fn validate_identity(world: &mut DuniterWorld, from: String, to: String) -> Result<()> {
// input names to keyrings
let from = AccountKeyring::from_str(&from).expect("unknown from");
let to: u32 = common::identity::get_identity_index(world, to).await?;
let to: u32 = common::identity::get_identity_index(world, to)
.await
.unwrap();
common::identity::validate_identity(world.client(), from, to).await
}
#[when(regex = r#"([a-zA-Z]+) requests distance evaluation for ([a-zA-Z]+)"#)]
async fn request_distance_evaluation(
world: &mut DuniterWorld,
from: String,
to: String,
) -> Result<()> {
// input names to keyrings
let from = AccountKeyring::from_str(&from).expect("unknown from");
let to: u32 = common::identity::get_identity_index(world, to).await?;
#[when(regex = r#"([a-zA-Z]+) requests distance evaluation"#)]
async fn request_distance_evaluation(world: &mut DuniterWorld, who: String) -> Result<()> {
let who = AccountKeyring::from_str(&who).expect("unknown origin");
common::distance::request_evaluation(world.client(), from, to).await
common::distance::request_evaluation(world.client(), who).await
}
#[when(regex = r#"distance oracle runs"#)]
async fn run_distance_oracle(world: &mut DuniterWorld) -> Result<()> {
common::spawn_distance_oracle(
common::DISTANCE_ORACLE_LOCAL_PATH,
world.inner.as_ref().unwrap().ws_port,
);
Ok(())
#[when(regex = r#"([a-zA-Z]+) runs distance oracle"#)]
async fn run_distance_oracle(world: &mut DuniterWorld, who: String) -> Result<()> {
let who = AccountKeyring::from_str(&who).expect("unknown origin");
common::distance::run_oracle(
world.client(),
who,
format!("ws://127.0.0.1:{}", world.inner.as_ref().unwrap().ws_port),
)
.await
}
// ===== then ====
......@@ -513,14 +512,19 @@ async fn should_have_distance_ok(world: &mut DuniterWorld, who: String) -> Resul
.await?
.unwrap();
if world
.read(&gdev::storage().distance().distance_ok_identities(idty_id))
match world
.read(
&gdev::storage()
.distance()
.identities_distance_status(idty_id),
)
.await?
.unwrap()
{
Ok(())
} else {
Err(anyhow::anyhow!("no evaluation in given pool").into())
Some(gdev::runtime_types::pallet_distance::types::DistanceStatus::Valid) => Ok(()),
Some(gdev::runtime_types::pallet_distance::types::DistanceStatus::Pending) => {
Err(anyhow::anyhow!("pending distance status").into())
}
None => Err(anyhow::anyhow!("no distance status").into()),
}
}
......
......@@ -20,12 +20,12 @@ mod median;
mod traits;
mod types;
use median::*;
pub use pallet::*;
pub use traits::*;
pub use types::*;
use frame_support::traits::StorageVersion;
//use median_accumulator::*;
use pallet_authority_members::SessionIndex;
use sp_distance::{InherentError, INHERENT_IDENTIFIER};
use sp_inherents::{InherentData, InherentIdentifier};
......@@ -36,7 +36,7 @@ type IdtyIndex = u32;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{pallet_prelude::*, traits::ReservableCurrency, BoundedBTreeSet};
use frame_support::{pallet_prelude::*, traits::ReservableCurrency};
use frame_system::pallet_prelude::*;
use sp_runtime::Perbill;
......@@ -65,6 +65,7 @@ pub mod pallet {
/// Maximum number of identities to be evaluated in a session
type MaxEvaluationsPerSession: Get<u32>;
/// Maximum number of evaluators in a session
#[pallet::constant]
type MaxEvaluatorsPerSession: Get<u32>;
/// Minimum ratio of accessible referees
type MinAccessibleReferees: Get<Perbill>;
......@@ -74,46 +75,39 @@ pub mod pallet {
// STORAGE //
pub type EvaluationPool<
AccountId,
IdtyIndex,
MaxEvaluationsPerSession,
MaxEvaluatorsPerSession,
> = (
BoundedVec<(IdtyIndex, MedianAcc<Perbill>), MaxEvaluationsPerSession>,
BoundedBTreeSet<AccountId, MaxEvaluatorsPerSession>,
);
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_0)]
pub type EvaluationPool0<T: Config<I>, I: 'static = ()> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_1)]
pub type EvaluationPool1<T: Config<I>, I: 'static = ()> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_2)]
pub type EvaluationPool2<T: Config<I>, I: 'static = ()> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
>,
ValueQuery,
>;
......@@ -209,66 +203,24 @@ pub mod pallet {
!DidUpdate::<T, I>::exists(),
Error::<T, I>::ManyEvaluationsInBlock,
);
let author = pallet_authorship::Pallet::<T>::author().ok_or(Error::<T, I>::NoAuthor)?;
Pallet::<T, I>::mutate_current_pool(
pallet_session::CurrentIndex::<T>::get().wrapping_add(1),
|result_pool| {
ensure!(
computation_result.distances.len() == result_pool.0.len(),
Error::<T, I>::WrongResultLength
);
let author =
pallet_authorship::Pallet::<T>::author().ok_or(Error::<T, I>::NoAuthor)?;
if result_pool
.1
.try_insert(author)
.map_err(|_| Error::<T, I>::TooManyEvaluators)?
{
for (distance_value, (_identity, median_acc)) in computation_result
.distances
.into_iter()
.zip(result_pool.0.iter_mut())
{
median_acc.push(distance_value);
}
Pallet::<T, I>::do_update_evaluation(author, computation_result)?;
DidUpdate::<T, I>::set(true);
Ok(())
} else {
Err(Error::<T, I>::ManyEvaluationsByAuthor.into())
}
},
)
}
}
// PUBLIC FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub fn do_evaluate_distance(
who: T::AccountId,
idty_index: <T as pallet_certification::Config<I>>::IdtyIndex,
) -> Result<(), DispatchError> {
Pallet::<T, I>::mutate_current_pool(
pallet_session::CurrentIndex::<T>::get(),
|current_pool| {
ensure!(
current_pool.0.len()
< (<T as Config<I>>::MaxEvaluationsPerSession::get() as usize),
Error::<T, I>::QueueFull
);
T::Currency::reserve(&who, <T as Config<I>>::EvaluationPrice::get())?;
current_pool
.0
.try_push((idty_index, median::MedianAcc::new()))
.map_err(|_| Error::<T, I>::QueueFull)?;
/// Push an evaluation result to the pool
#[pallet::weight(1_000_000_000)]
pub fn force_update_evaluation(
origin: OriginFor<T>,
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
ensure_root(origin)?;
Ok(())
},
)
Pallet::<T, I>::do_update_evaluation(evaluator, computation_result)
}
}
......@@ -283,7 +235,7 @@ pub mod pallet {
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
>,
) -> R,
>(
......@@ -294,7 +246,7 @@ pub mod pallet {
0 => EvaluationPool2::<T, I>::mutate(f),
1 => EvaluationPool0::<T, I>::mutate(f),
2 => EvaluationPool1::<T, I>::mutate(f),
_ => panic!("index % 3 < 3"),
_ => unreachable!("index % 3 < 3"),
}
}
......@@ -306,15 +258,75 @@ pub mod pallet {
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
> {
match index % 3 {
0 => EvaluationPool2::<T, I>::take(),
1 => EvaluationPool0::<T, I>::take(),
2 => EvaluationPool1::<T, I>::take(),
_ => panic!("index % 3 < 3"),
_ => unreachable!("index % 3 < 3"),
}
}
fn do_evaluate_distance(
who: T::AccountId,
idty_index: <T as pallet_certification::Config<I>>::IdtyIndex,
) -> Result<(), DispatchError> {
Pallet::<T, I>::mutate_current_pool(
pallet_session::CurrentIndex::<T>::get(),
|current_pool| {
ensure!(
current_pool.0.len()
< (<T as Config<I>>::MaxEvaluationsPerSession::get() as usize),
Error::<T, I>::QueueFull
);
T::Currency::reserve(&who, <T as Config<I>>::EvaluationPrice::get())?;
current_pool
.0
.try_push((idty_index, median::MedianAcc::new()))
.map_err(|_| Error::<T, I>::QueueFull)?;
IdentitiesDistanceStatus::<T, I>::insert(idty_index, DistanceStatus::Pending);
Ok(())
},
)
}
fn do_update_evaluation(
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
Pallet::<T, I>::mutate_current_pool(
pallet_session::CurrentIndex::<T>::get().wrapping_add(1),
|result_pool| {
ensure!(
computation_result.distances.len() == result_pool.0.len(),
Error::<T, I>::WrongResultLength
);
if result_pool
.1
.try_insert(evaluator)
.map_err(|_| Error::<T, I>::TooManyEvaluators)?
{
for (distance_value, (_identity, median_acc)) in computation_result
.distances
.into_iter()
.zip(result_pool.0.iter_mut())
{
median_acc.push(distance_value);
}
Ok(())
} else {
Err(Error::<T, I>::ManyEvaluationsByAuthor.into())
}
},
)
}
}
impl<T: Config<I>, I: 'static> pallet_authority_members::OnNewSession for Pallet<T, I> {
......@@ -328,7 +340,7 @@ pub mod pallet {
<T as frame_system::Config>::AccountId,
<T as pallet_certification::Config<I>>::IdtyIndex,
<T as Config<I>>::MaxEvaluationsPerSession,
<T as Config<I>>::MaxEvaluatorsPerSession,
100, //<T as Config<I>>::MaxEvaluatorsPerSession,
> = Pallet::<T, I>::take_current_pool(index);
for (idty, median_acc) in current_pool.0.into_iter() {
let Some(idty_value) =
......@@ -340,7 +352,9 @@ pub mod pallet {
MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1)
};
if median >= T::MinAccessibleReferees::get() {
IdentitiesDistanceStatus::<T, I>::insert(idty, DistanceStatus::Valid);
IdentitiesDistanceStatus::<T, I>::mutate(idty, |status| {
*status = Some(DistanceStatus::Valid)
});
T::Currency::unreserve(
&account_id,
<T as Config<I>>::EvaluationPrice::get(),
......
......@@ -15,25 +15,38 @@
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use frame_support::pallet_prelude::*;
use sp_std::{cmp::Ordering, vec::Vec};
use sp_std::cmp::Ordering;
#[derive(Clone, Debug, Decode, Default, Encode, TypeInfo)]
pub struct MedianAcc<T: Clone + Decode + Encode + Ord + TypeInfo> {
samples: Vec<(T, u32)>,
pub struct MedianAcc<
T: Clone + Decode + Encode + Ord + TypeInfo,
const S: u32, /*Get<u32> + TypeInfo*/
> {
samples: BoundedVec<(T, u32), ConstU32<S>>,
median_index: Option<u32>,
median_subindex: u32,
}
/*impl<T: 'static + Clone + Decode + Encode + Ord + TypeInfo, S: 'static + Get<u32>> TypeInfo
for MedianAcc<T, S>
{
type Identity = Self;
fn type_info() -> scale_info::Type<scale_info::form::MetaForm> {}
}*/
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MedianResult<T: Clone + Ord> {
One(T),
Two(T, T),
}
impl<T: Clone + Decode + Encode + Ord + TypeInfo> MedianAcc<T> {
impl<T: Clone + Decode + Encode + Ord + TypeInfo, const S: u32 /*Get<u32> + TypeInfo*/>
MedianAcc<T, S>
{
pub fn new() -> Self {
Self {
samples: Vec::new(),
samples: BoundedVec::default(),
median_index: None,
median_subindex: 0,
}
......@@ -84,7 +97,7 @@ impl<T: Clone + Decode + Encode + Ord + TypeInfo> MedianAcc<T> {
}
}
Err(sample_index) => {
self.samples.insert(sample_index, (sample, 1));
self.samples.try_insert(sample_index, (sample, 1)).ok();
if *median_index as usize >= sample_index {
if self.median_subindex == 0 {
self.median_subindex = self
......@@ -115,7 +128,7 @@ impl<T: Clone + Decode + Encode + Ord + TypeInfo> MedianAcc<T> {
}
}
} else {
self.samples.push((sample, 1));
self.samples.try_push((sample, 1)).ok();
self.median_index = Some(0);
}
}
......
......@@ -14,10 +14,12 @@
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub use crate::median::*;
pub use sp_distance::ComputationResult;
use codec::{Decode, Encode};
use frame_support::pallet_prelude::*;
use frame_support::{pallet_prelude::*, BoundedBTreeSet};
use sp_runtime::Perbill;
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DistanceStatus {
......@@ -26,3 +28,16 @@ pub enum DistanceStatus {
/// Identity respects the distance
Valid,
}
pub type EvaluationPool<
AccountId,
IdtyIndex,
MaxEvaluationsPerSession,
const MAX_EVALUATORS_PER_SESSION: u32,
> = (
BoundedVec<
(IdtyIndex, MedianAcc<Perbill, MAX_EVALUATORS_PER_SESSION>),
MaxEvaluationsPerSession,
>,
BoundedBTreeSet<AccountId, ConstU32<MAX_EVALUATORS_PER_SESSION>>,
);
No preview for this file type
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment