From 47256e5754e15a325d2682804f459c90bb1429e5 Mon Sep 17 00:00:00 2001 From: tuxmain <tuxmain@zettascript.org> Date: Mon, 19 Jun 2023 14:56:38 +0200 Subject: [PATCH] wip --- Cargo.lock | 21 ++++++- distance-oracle/Cargo.toml | 2 + distance-oracle/src/api.rs | 98 +++++++++++++++++++++++++++++++ distance-oracle/src/impls.rs | 7 +++ distance-oracle/src/lib.rs | 104 +++++++++------------------------ distance-oracle/src/mock.rs | 1 + distance-oracle/src/tests.rs | 2 + pallets/distance/Cargo.toml | 3 + pallets/distance/src/lib.rs | 16 ++--- pallets/distance/src/median.rs | 14 ++--- pallets/distance/src/types.rs | 20 ++++++- resources/metadata.scale | Bin 132872 -> 132702 bytes 12 files changed, 193 insertions(+), 95 deletions(-) create mode 100644 distance-oracle/src/api.rs create mode 100644 distance-oracle/src/impls.rs create mode 100644 distance-oracle/src/mock.rs create mode 100644 distance-oracle/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a38e38629..e4cbdab30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -653,6 +653,12 @@ dependencies = [ "jobserver", ] +[[package]] +name = "cc-traits" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "becb23f44ae9dd141d637d520b908c291bf4c5eed016ca368daa1430d54ebf4c" + [[package]] name = "cfg-expr" version = "0.10.3" @@ -1558,7 +1564,9 @@ dependencies = [ name = "distance-oracle" version = "0.1.0" dependencies = [ + "cc-traits", "clap 4.1.4", + "median-accumulator", "parity-scale-codec", "rayon", "sp-core", @@ -4367,6 +4375,15 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "median-accumulator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9777055bcc453173875aab61f856474ec399120e8828fd02093841fc602eb73a" +dependencies = [ + "cc-traits", +] + [[package]] name = "memchr" version = "2.5.0" @@ -5203,8 +5220,10 @@ dependencies = [ name = "pallet-distance" version = "1.0.0" dependencies = [ + "cc-traits", "frame-support", "frame-system", + "median-accumulator", "pallet-authority-members", "pallet-authorship", "pallet-certification", @@ -9798,7 +9817,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", diff --git a/distance-oracle/Cargo.toml b/distance-oracle/Cargo.toml index 8869a5fc6..8c80fb6f6 100644 --- a/distance-oracle/Cargo.toml +++ b/distance-oracle/Cargo.toml @@ -9,7 +9,9 @@ edition = "2021" [dependencies] sp-distance = { path = "../primitives/distance" } +cc-traits = "1.0.0" codec = { package = "parity-scale-codec", version = "3.1.5" } +median-accumulator = { version = "0.2.0", features = ["nostd"] } 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" } diff --git a/distance-oracle/src/api.rs b/distance-oracle/src/api.rs new file mode 100644 index 000000000..6a177aeeb --- /dev/null +++ b/distance-oracle/src/api.rs @@ -0,0 +1,98 @@ +use crate::{AccountId, EvaluationPool, IdtyIndex}; + +use codec::Encode; +use sp_core::H256; +use subxt::ext::sp_runtime::Perbill; +use subxt::storage::StorageKey; + +#[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 = AccountId; + type Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>; + type Header = subxt::ext::sp_runtime::generic::Header< + Self::BlockNumber, + subxt::ext::sp_runtime::traits::BlakeTwo256, + >; + type Signature = subxt::ext::sp_runtime::MultiSignature; + type ExtrinsicParams = subxt::tx::BaseExtrinsicParams<Self, Tip>; +} + +#[derive(Copy, Clone, Debug, Default, Encode)] +pub struct Tip { + #[codec(compact)] + tip: u64, +} + +impl Tip { + pub fn new(amount: u64) -> Self { + Tip { tip: amount } + } +} + +impl From<u64> for Tip { + fn from(n: u64) -> Self { + Self::new(n) + } +} + +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<MaxEvaluationsPerSession, MaxEvaluatorsPerSession>( + client: &Client, + parent_hash: H256, + current_session: u32, +) -> Option<EvaluationPool<AccountId, IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession>> +{ + 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 +} + +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() +} diff --git a/distance-oracle/src/impls.rs b/distance-oracle/src/impls.rs new file mode 100644 index 000000000..7ceed4f23 --- /dev/null +++ b/distance-oracle/src/impls.rs @@ -0,0 +1,7 @@ +impl<T, S> cc_traits::GetMut<T> for sp_core::bounded::BoundedVec<T, S> {} + +impl<T, S> cc_traits::Len for sp_core::bounded::BoundedVec<T, S> { + fn len(&self) -> usize { + self.len() + } +} diff --git a/distance-oracle/src/lib.rs b/distance-oracle/src/lib.rs index 62fc97b5d..83d94f5f2 100644 --- a/distance-oracle/src/lib.rs +++ b/distance-oracle/src/lib.rs @@ -1,5 +1,18 @@ +#[cfg(not(test))] +mod api; +mod impls; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(not(test))] +use api::{gdev, Client}; + use codec::Encode; +use median_accumulator::*; use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use sp_core::bounded::{BoundedBTreeSet, BoundedVec}; use std::collections::{HashMap, HashSet}; use std::io::Write; use std::path::PathBuf; @@ -22,80 +35,29 @@ impl Default for Settings { } } -#[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 Address = subxt::ext::sp_runtime::MultiAddress<Self::AccountId, u32>; - type Header = subxt::ext::sp_runtime::generic::Header< - Self::BlockNumber, - subxt::ext::sp_runtime::traits::BlakeTwo256, - >; - type Signature = subxt::ext::sp_runtime::MultiSignature; - type ExtrinsicParams = subxt::tx::BaseExtrinsicParams<Self, Tip>; -} - -#[derive(Copy, Clone, Debug, Default, Encode)] -pub struct Tip { - #[codec(compact)] - tip: u64, -} - -impl Tip { - pub fn new(amount: u64) -> Self { - Tip { tip: amount } - } -} - -impl From<u64> for Tip { - fn from(n: u64) -> Self { - Self::new(n) - } -} - +type AccountId = subxt::ext::sp_runtime::AccountId32; type IdtyIndex = u32; +type EvaluationPool<AccountId, IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession> = ( + BoundedVec< + ( + IdtyIndex, + MedianAcc<Perbill, BoundedVec<Perbill, MaxEvaluatorsPerSession>>, + ), + MaxEvaluationsPerSession, + >, + BoundedBTreeSet<AccountId, MaxEvaluatorsPerSession>, +); + pub async fn run(settings: Settings) { let client = Client::from_url(settings.rpc_url).await.unwrap(); - let parent_hash = client - .storage() - .fetch(&gdev::storage().system().parent_hash(), None) - .await - .unwrap() - .unwrap(); + let parent_hash = api::parent_hash(&client).await; - let current_session = client - .storage() - .fetch( - &gdev::storage().session().current_index(), - Some(parent_hash), - ) - .await - .unwrap() - .unwrap_or_default(); + 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 + let Some(evaluation_pool) = api::current_pool(&client, parent_hash, current_session).await .unwrap() else { println!("Pool does not exist"); return @@ -117,15 +79,7 @@ pub async fn run(settings: Settings) { return; } - let evaluation_block = client - .storage() - .fetch( - &gdev::storage().distance().evaluation_block(), - Some(parent_hash), - ) - .await - .unwrap() - .unwrap(); + let evaluation_block = api::evaluation_block(&client, parent_hash).await; std::fs::create_dir_all(&settings.evaluation_result_dir).unwrap(); diff --git a/distance-oracle/src/mock.rs b/distance-oracle/src/mock.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/distance-oracle/src/mock.rs @@ -0,0 +1 @@ + diff --git a/distance-oracle/src/tests.rs b/distance-oracle/src/tests.rs new file mode 100644 index 000000000..0c833b61a --- /dev/null +++ b/distance-oracle/src/tests.rs @@ -0,0 +1,2 @@ +#[test] +fn test_distance_validity() {} diff --git a/pallets/distance/Cargo.toml b/pallets/distance/Cargo.toml index 47f1f6710..b2cb197a9 100644 --- a/pallets/distance/Cargo.toml +++ b/pallets/distance/Cargo.toml @@ -34,6 +34,9 @@ pallet-identity = { path = "../identity", default-features = false } pallet-membership = { path = "../membership", default-features = false } sp-distance = { path = "../../primitives/distance", default-features = false } +cc-traits = "1.0.0" +median-accumulator = { version = "0.2.0", features = ["nostd"] } + # substrate scale-info = { version = "2.1.1", default-features = false, features = [ "derive", diff --git a/pallets/distance/src/lib.rs b/pallets/distance/src/lib.rs index cce777e7d..09c38cbb2 100644 --- a/pallets/distance/src/lib.rs +++ b/pallets/distance/src/lib.rs @@ -26,6 +26,7 @@ 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}; @@ -65,7 +66,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 - type MaxEvaluatorsPerSession: Get<u32>; + type MaxEvaluatorsPerSession: Get<u32> + TypeInfo; /// Minimum ratio of accessible referees type MinAccessibleReferees: Get<Perbill>; // /// Handler for the evaluation price in case of negative result @@ -74,17 +75,8 @@ 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< @@ -96,6 +88,7 @@ pub mod pallet { ValueQuery, >; #[pallet::storage] + #[pallet::getter(fn evaluation_pool_1)] pub type EvaluationPool1<T: Config<I>, I: 'static = ()> = StorageValue< _, EvaluationPool< @@ -107,6 +100,7 @@ pub mod pallet { ValueQuery, >; #[pallet::storage] + #[pallet::getter(fn evaluation_pool_2)] pub type EvaluationPool2<T: Config<I>, I: 'static = ()> = StorageValue< _, EvaluationPool< diff --git a/pallets/distance/src/median.rs b/pallets/distance/src/median.rs index 065efb311..a5f078d78 100644 --- a/pallets/distance/src/median.rs +++ b/pallets/distance/src/median.rs @@ -14,12 +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/>. -use frame_support::pallet_prelude::*; +use frame_support::{pallet_prelude::*, traits::*}; use sp_std::{cmp::Ordering, vec::Vec}; #[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, S: Get<u32> + TypeInfo> { + samples: BoundedVec<(T, u32), S>, median_index: Option<u32>, median_subindex: u32, } @@ -30,10 +30,10 @@ pub enum MedianResult<T: Clone + Ord> { Two(T, T), } -impl<T: Clone + Decode + Encode + Ord + TypeInfo> MedianAcc<T> { +impl<T: Clone + Decode + Encode + Ord + TypeInfo, S: 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 +84,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)); if *median_index as usize >= sample_index { if self.median_subindex == 0 { self.median_subindex = self @@ -115,7 +115,7 @@ impl<T: Clone + Decode + Encode + Ord + TypeInfo> MedianAcc<T> { } } } else { - self.samples.push((sample, 1)); + self.samples.try_push((sample, 1)); self.median_index = Some(0); } } diff --git a/pallets/distance/src/types.rs b/pallets/distance/src/types.rs index 2d3edf946..ab629ec48 100644 --- a/pallets/distance/src/types.rs +++ b/pallets/distance/src/types.rs @@ -14,10 +14,13 @@ // 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 median_accumulator::*; +use sp_runtime::Perbill; #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum DistanceStatus { @@ -26,3 +29,18 @@ pub enum DistanceStatus { /// Identity respects the distance Valid, } + +pub type EvaluationPool<AccountId, IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession> = ( + BoundedVec<(IdtyIndex, MedianAcc<Perbill, MaxEvaluatorsPerSession>), MaxEvaluationsPerSession>, + BoundedBTreeSet<AccountId, MaxEvaluatorsPerSession>, +); + +/*pub struct BoundedVecC<T, S>(BoundedVec<T, S>); + +impl<T, S> cc_traits::GetMut<T> for BoundedVecC<T, S> {} + +impl<T, S> cc_traits::Len for BoundedVecC<T, S> { + fn len(&self) -> usize { + self.0.len() + } +}*/ diff --git a/resources/metadata.scale b/resources/metadata.scale index c5d992edbe02769551ed0a8ba738d79232bccfbe..d877488391b6849628755f61564641ad0e10c5e8 100644 GIT binary patch delta 7812 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 delta 7670 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 -- GitLab