diff --git a/Cargo.lock b/Cargo.lock index e2b6bba2a56bdd125475db9f94084b56e52c89d6..416de18697d326dcb43e3352c0ba27853c4a1554 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,9 +377,9 @@ checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "beef" @@ -819,6 +819,8 @@ dependencies = [ "pallet-babe", "pallet-balances", "pallet-certification", + "pallet-distance", + "pallet-distance-rpc-runtime-api", "pallet-duniter-account", "pallet-duniter-wot", "pallet-grandpa", @@ -842,6 +844,7 @@ dependencies = [ "sp-arithmetic", "sp-consensus-babe", "sp-core", + "sp-distance", "sp-membership", "sp-runtime", "sp-staking", @@ -1328,6 +1331,26 @@ dependencies = [ "syn", ] +[[package]] +name = "dc-distance" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-distance", + "parity-scale-codec", + "sc-client-api", + "scale-info", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-distance", + "sp-rpc", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "der" version = "0.5.1" @@ -1459,6 +1482,7 @@ dependencies = [ "clap", "clap_complete", "common-runtime", + "dc-distance", "frame-benchmarking", "frame-benchmarking-cli", "futures 0.3.21", @@ -1472,6 +1496,8 @@ dependencies = [ "maplit", "memmap2 0.5.0", "pallet-certification", + "pallet-distance-rpc", + "pallet-distance-rpc-runtime-api", "pallet-grandpa", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api", @@ -1502,6 +1528,7 @@ dependencies = [ "sp-consensus", "sp-consensus-babe", "sp-core", + "sp-distance", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -2419,6 +2446,8 @@ dependencies = [ "pallet-balances", "pallet-certification", "pallet-collective", + "pallet-distance", + "pallet-distance-rpc-runtime-api", "pallet-duniter-account", "pallet-duniter-test-parameters", "pallet-duniter-wot", @@ -2453,6 +2482,7 @@ dependencies = [ "sp-consensus-babe", "sp-consensus-vrf", "sp-core", + "sp-distance", "sp-finality-grandpa", "sp-inherents", "sp-io", @@ -2905,13 +2935,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.4" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 0.4.7", + "itoa 1.0.1", ] [[package]] @@ -5028,6 +5058,59 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-distance" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-authority-members", + "pallet-authorship", + "pallet-certification", + "pallet-identity", + "pallet-membership", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-distance", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-distance-rpc" +version = "1.0.0" +dependencies = [ + "base64", + "frame-support", + "frame-system", + "jsonrpsee 0.14.0", + "pallet-distance-rpc-runtime-api", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-distance", + "sp-rpc", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-distance-rpc-runtime-api" +version = "1.0.0" +dependencies = [ + "frame-support", + "pallet-distance", + "parity-scale-codec", + "sp-api", + "sp-runtime", +] + [[package]] name = "pallet-duniter-account" version = "3.0.0" @@ -8218,6 +8301,21 @@ dependencies = [ "syn", ] +[[package]] +name = "sp-distance" +version = "3.0.0" +dependencies = [ + "async-trait", + "frame-support", + "parity-scale-codec", + "scale-info", + "serde", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + [[package]] name = "sp-externalities" version = "0.12.0" @@ -9374,9 +9472,9 @@ 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.3", - "rand 0.7.3", + "rand 0.8.4", "static_assertions", ] diff --git a/Cargo.toml b/Cargo.toml index 16e11ef4537fe2323835a5ff30f6225e77070f4e..06638309b3c7b2ffefd702315f3a4f8df15bbd02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,10 +55,14 @@ sp-trie = { git = "https://github.com/duniter/substrate", branch = "duniter-subs [dependencies] # local dependencies common-runtime = { path = 'runtime/common' } +dc-distance = { path = 'client/distance' } g1-runtime = { path = 'runtime/g1', optional = true } gdev-runtime = { path = 'runtime/gdev', optional = true } gtest-runtime = { path = 'runtime/gtest', optional = true } pallet-certification = { path = 'pallets/certification' } +pallet-distance-rpc = { path = 'pallets/distance/rpc' } +pallet-distance-rpc-runtime-api = { path = 'pallets/distance/rpc/runtime-api' } +sp-distance = { path = 'primitives/distance' } sp-membership = { path = 'primitives/membership' } # crates.io dependencies @@ -125,9 +129,13 @@ try-runtime-cli = { git = "https://github.com/duniter/substrate", branch = "duni resolver = "2" members = [ + 'client/distance', 'end2end-tests', 'live-tests', 'pallets/certification', + 'pallets/distance', + 'pallets/distance/rpc', + 'pallets/distance/rpc/runtime-api', 'pallets/duniter-test-parameters', 'pallets/duniter-test-parameters/macro', 'pallets/duniter-wot', @@ -138,6 +146,7 @@ members = [ 'pallets/universal-dividend', 'pallets/upgrade-origin', 'primitives/membership', + 'primitives/distance', 'runtime/common', 'runtime/gdev', 'xtask', diff --git a/client/distance/Cargo.toml b/client/distance/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6b29a20f43a21256634ce95a94258d4b144b709a --- /dev/null +++ b/client/distance/Cargo.toml @@ -0,0 +1,65 @@ +[package] +authors = ['tuxmain <tuxmain@zettascript.org>'] +description = 'Duniter client distance' +edition = '2018' +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +name = 'dc-distance' +readme = 'README.md' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '1.0.0' + +[dependencies] + +pallet-distance = { path = "../../pallets/distance" } +sp-distance = { path = "../../primitives/distance" } +thiserror = "1.0.30" + +# substrate +scale-info = { version = "2.1.1", features = ["derive"] } + +[dependencies.codec] +features = ['derive'] +package = 'parity-scale-codec' +version = '3.1.5' + +[dependencies.frame-support] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.frame-system] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sc-client-api] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-api] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-blockchain] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-core] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-rpc] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-runtime] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-std] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +### DOC ### + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] diff --git a/client/distance/src/lib.rs b/client/distance/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc7ceb499ca6fe22b25c1317be5ce1fbd2a4e455 --- /dev/null +++ b/client/distance/src/lib.rs @@ -0,0 +1,70 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use codec::{Decode, Encode}; +use sc_client_api::{ProvideUncles, StorageKey, StorageProvider}; +use scale_info::TypeInfo; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +pub enum Error<B: BlockT> { + #[error("Could not retrieve the block hash for block id: {0:?}")] + NoHashForBlockId(BlockId<B>), +} + +/// Create a new [`sp_distance::InherentDataProvider`] at the given block. +pub fn create_distance_inherent_data_provider<B, C, Backend, IdtyIndex>( + client: &C, + parent: B::Hash, + distance_dir: PathBuf, +) -> Result<sp_distance::InherentDataProvider<IdtyIndex>, sc_client_api::blockchain::Error> +where + B: BlockT, + C: ProvideUncles<B> + StorageProvider<B, Backend>, + Backend: sc_client_api::Backend<B>, + IdtyIndex: Decode + Encode + PartialEq + TypeInfo, +{ + // TODO: + // * dc_distance::create_distance_inherent_data_provider(client_clone, parent, distance_dir) + // * est-ce qu'on a déjà publié un résultat ce round + // * avec storage provider dans client_clone: + // * récupérer liste des forgerons ayant publié résultat ce round + // * palette session: dans KeyOwner: récupérer notre clé forgeron à partir d'une de nos session_keys + // * sinon, pour quel round on veut un résultat + // * current_round_index dans storage provider (ou session index) + + let key_published_results = StorageKey( + frame_support::storage::storage_prefix(b"Distance", b"StoragePublishedResults").to_vec(), + ); + // TODO: what if unwrap fails? + let published_results = client + .storage(&BlockId::Hash(parent), &key_published_results)? + .unwrap(); + + let key_owner_key = + StorageKey(frame_support::storage::storage_prefix(b"Session", b"OwnerKey").to_vec()); + // TODO: what if unwrap fails? + let owner_key = client + .storage(&BlockId::Hash(parent), &key_owner_key)? + .unwrap(); + + // TODO pour trouver session key: voir keystore + + Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new( + distance_dir, + )) +} diff --git a/node/src/rpc.rs b/node/src/rpc.rs index 36dee008a8e037c36cd0b4430a120189b878fb2d..92c6b722140cda22cea76790d56f561c5b2ce75c 100644 --- a/node/src/rpc.rs +++ b/node/src/rpc.rs @@ -24,13 +24,13 @@ pub use sc_rpc_api::DenyUnsafe; use common_runtime::Block; -use common_runtime::{AccountId, Balance, Index}; +use common_runtime::{AccountId, Balance, IdtyIndex, Index}; use jsonrpsee::RpcModule; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; -use std::sync::Arc; +use std::{path::PathBuf, sync::Arc}; /// Full client dependencies. pub struct FullDeps<C, P> { @@ -43,6 +43,7 @@ pub struct FullDeps<C, P> { /// Manual seal command sink pub command_sink_opt: Option<futures::channel::mpsc::Sender<manual_seal::EngineCommand<sp_core::H256>>>, + pub distance_dir: PathBuf, } /// Instantiate all full RPC extensions. @@ -54,11 +55,13 @@ where C: HeaderBackend<Block> + HeaderMetadata<Block, Error = BlockChainError> + 'static, C: Send + Sync + 'static, C::Api: substrate_frame_rpc_system::AccountNonceApi<Block, AccountId, Index>, + C::Api: pallet_distance_rpc::DistanceRuntimeApi<Block, IdtyIndex>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi<Block, Balance>, C::Api: BlockBuilder<Block>, P: TransactionPool + 'static, { use manual_seal::rpc::{ManualSeal, ManualSealApiServer}; + use pallet_distance_rpc::{Distance, DistanceApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer}; @@ -68,10 +71,11 @@ where pool, deny_unsafe, command_sink_opt, + distance_dir, } = deps; module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; - module.merge(TransactionPayment::new(client).into_rpc())?; + module.merge(TransactionPayment::new(client.clone()).into_rpc())?; if let Some(command_sink) = command_sink_opt { // We provide the rpc handler with the sending end of the channel to allow the rpc @@ -83,6 +87,13 @@ where // `YourRpcStruct` should have a reference to a client, which is needed // to call into the runtime. // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` + /*module.merge(DistanceApiServer::into_rpc( + Distance::new( + client, + distance_dir, + ), + ))?;*/ + module.merge(Distance::new(client, distance_dir).into_rpc())?; Ok(module) } diff --git a/node/src/service.rs b/node/src/service.rs index e668b5383d927772c92e23256f971a8480f1150f..4ecbca22c2e8126d97681b51b8238e888180e8ff 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -31,7 +31,7 @@ use sc_service::{error::Error as ServiceError, Configuration, PartialComponents, use sc_telemetry::{Telemetry, TelemetryWorker}; use sp_core::H256; use sp_runtime::traits::BlakeTwo256; -use std::{sync::Arc, time::Duration}; +use std::{path::PathBuf, sync::Arc, time::Duration}; type FullClient<RuntimeApi, Executor> = sc_service::TFullClient<Block, RuntimeApi, NativeElseWasmExecutor<Executor>>; @@ -431,6 +431,15 @@ where let enable_grandpa = !config.disable_grandpa; let prometheus_registry = config.prometheus_registry().cloned(); + let distance_dir = config.base_path.as_ref().map_or_else( + || PathBuf::from("/tmp"), + |base_path| { + base_path + .config_dir(config.chain_spec.id()) + .join("distance") + }, + ); // TODO improve this + let mut command_sink_opt = None; if role.is_authority() { let proposer_factory = sc_basic_authorship::ProposerFactory::new( @@ -525,6 +534,7 @@ where let client_clone = client.clone(); let slot_duration = babe_link.config().slot_duration(); + let distance_dir = distance_dir.clone(); let babe_config = babe::BabeParams { keystore: keystore_container.sync_keystore(), client: client.clone(), @@ -535,6 +545,7 @@ where justification_sync_link: network.clone(), create_inherent_data_providers: move |parent, ()| { let client_clone = client_clone.clone(); + let distance_dir = distance_dir.clone(); async move { let uncles = sc_consensus_uncles::create_uncles_inherent_data_provider( @@ -550,7 +561,15 @@ where slot_duration, ); - Ok((timestamp, slot, uncles)) + let distance = + dc_distance::create_distance_inherent_data_provider::< + _, + _, + FullBackend, + u32, + >(&*client_clone, parent, distance_dir)?; + + Ok((timestamp, slot, uncles, distance)) } }, force_authoring, @@ -579,6 +598,7 @@ where let pool = transaction_pool.clone(); //let select_chain = select_chain.clone(); //let chain_spec = config.chain_spec.cloned_box(); + let distance_dir = distance_dir.clone(); Box::new(move |deny_unsafe, _| { let deps = crate::rpc::FullDeps { @@ -593,6 +613,7 @@ where keystore: keystore.clone(), },*/ command_sink_opt: command_sink_opt.clone(), + distance_dir: distance_dir.clone(), }; crate::rpc::create_full(deps).map_err(Into::into) diff --git a/node/src/service/client.rs b/node/src/service/client.rs index 9c5d63ae509aaf5447f625c75c79619ba808da7c..b1deec86edd12335baaf139362d8cdd831795018 100644 --- a/node/src/service/client.rs +++ b/node/src/service/client.rs @@ -14,7 +14,7 @@ // 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 common_runtime::{AccountId, Balance, Block, BlockNumber, Hash, Header, Index}; +use common_runtime::{AccountId, Balance, Block, BlockNumber, Hash, Header, IdtyIndex, Index}; use sc_client_api::{AuxStore, Backend as BackendT, BlockchainEvents, KeyIterator, UsageProvider}; use sp_api::{CallApiAt, NumberFor, ProvideRuntimeApi}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -112,6 +112,7 @@ pub trait ExecuteWithClient { /// that it contains. pub trait RuntimeApiCollection: pallet_grandpa::fg_primitives::GrandpaApi<Block> + + pallet_distance_rpc_runtime_api::DistanceApi<Block, IdtyIndex> + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> + sp_api::ApiExt<Block> + sp_authority_discovery::AuthorityDiscoveryApi<Block> @@ -129,6 +130,7 @@ where impl<Api> RuntimeApiCollection for Api where Api: pallet_grandpa::fg_primitives::GrandpaApi<Block> + + pallet_distance_rpc_runtime_api::DistanceApi<Block, IdtyIndex> + pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<Block, Balance> + sp_api::ApiExt<Block> + sp_authority_discovery::AuthorityDiscoveryApi<Block> diff --git a/pallets/authority-members/src/lib.rs b/pallets/authority-members/src/lib.rs index 941a0af7d56e1f4be886df51bc7905436b93f900..2d052c75fadd1882893c14e2ea393b7a0ff6474f 100644 --- a/pallets/authority-members/src/lib.rs +++ b/pallets/authority-members/src/lib.rs @@ -29,13 +29,13 @@ mod tests; /*#[cfg(feature = "runtime-benchmarks")] mod benchmarking;*/ +pub use self::traits::*; pub use pallet::*; +pub use sp_staking::SessionIndex; pub use types::*; -use self::traits::*; use frame_support::traits::Get; use sp_runtime::traits::Convert; -use sp_staking::SessionIndex; use sp_std::prelude::*; #[frame_support::pallet] diff --git a/pallets/distance/Cargo.toml b/pallets/distance/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b1c3cbf3af4edeca7e10c61b597665118c6926e9 --- /dev/null +++ b/pallets/distance/Cargo.toml @@ -0,0 +1,85 @@ +[package] +authors = ['tuxmain <tuxmain@zettascript.org>'] +description = 'FRAME pallet distance.' +edition = '2021' +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +name = 'pallet-distance' +readme = 'README.md' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '1.0.0' + +[features] +default = ['std'] +std = [ + 'codec/std', + 'frame-support/std', + 'frame-system/std', + 'pallet-session/std', + 'sp-core/std', + 'sp-distance/std', + 'sp-runtime/std', + 'sp-std/std', +] + +[dependencies] + +pallet-authority-members = { path = "../authority-members", default-features = false } +pallet-certification = { path = "../certification", default-features = false } +pallet-identity = { path = "../identity", default-features = false } +pallet-membership = { path = "../membership", default-features = false } +sp-distance = { path = "../../primitives/distance", default-features = false } + +# substrate +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '3.1.5' + +[dependencies.frame-support] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.frame-system] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.pallet-authorship] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.pallet-session] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-core] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-inherents] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-runtime] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-std] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +### DOC ### + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] diff --git a/pallets/distance/rpc/Cargo.toml b/pallets/distance/rpc/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..1feabb3b3a0c422cac77c4eccd661a545b5bb9ef --- /dev/null +++ b/pallets/distance/rpc/Cargo.toml @@ -0,0 +1,64 @@ +[package] +authors = ['tuxmain <tuxmain@zettascript.org>'] +description = 'FRAME pallet distance RPC.' +edition = '2018' +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +name = 'pallet-distance-rpc' +readme = 'README.md' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '1.0.0' + +[dependencies] + +pallet-distance-rpc-runtime-api = { path = "./runtime-api" } +sp-distance = { path = "../../../primitives/distance" } + +base64 = "0.13.1" +serde = { version = "1.0", features = ["derive"] } + +# substrate +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +scale-info = { version = "2.1.1", features = ["derive"] } + +[dependencies.codec] +features = ['derive'] +package = 'parity-scale-codec' +version = '3.1.5' + +[dependencies.frame-support] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.frame-system] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-api] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-blockchain] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-core] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-rpc] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-runtime] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-std] +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +### DOC ### + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] diff --git a/pallets/distance/rpc/runtime-api/Cargo.toml b/pallets/distance/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..c27fbca9855c011ac31e67c6b2b2f22dfff5a434 --- /dev/null +++ b/pallets/distance/rpc/runtime-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ['tuxmain <tuxmain@zettascript.org>'] +description = 'FRAME pallet distance RPC runtime API.' +edition = '2018' +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +name = 'pallet-distance-rpc-runtime-api' +readme = 'README.md' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '1.0.0' + +[dependencies] +pallet-distance = { path = "../../", default-features = false } +sp-api = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.26", default-features = false } +sp-runtime = { git = "https://github.com/duniter/substrate", branch = "duniter-substrate-v0.9.26", default-features = false } + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = '3.1.5' + +[dependencies.frame-support] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[features] +default = ["std"] +std = [ + "sp-api/std", +] diff --git a/pallets/distance/rpc/runtime-api/src/lib.rs b/pallets/distance/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..71aa3f9221239263840b66df242d9cf6df75ced8 --- /dev/null +++ b/pallets/distance/rpc/runtime-api/src/lib.rs @@ -0,0 +1,31 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet_distance::{ComputationMetadata, ComputationResult}; + +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::*; + +// Here we declare the runtime API. It is implemented it the `impl` block in +// runtime amalgamator file (the `runtime/src/lib.rs`) +sp_api::decl_runtime_apis! { + pub trait DistanceApi<IdtyIndex: Decode + Encode + PartialEq + TypeInfo> { + fn get_computation_metadata() -> Option<ComputationMetadata>; + //fn publish_computation_result(computation_result: ComputationResult<IdtyIndex>); + } +} diff --git a/pallets/distance/rpc/src/lib.rs b/pallets/distance/rpc/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..31e1a74ff5de53cb7ae13a4c2aa665708dab6663 --- /dev/null +++ b/pallets/distance/rpc/src/lib.rs @@ -0,0 +1,177 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Substrate-Libre-Currency. +// +// Substrate-Libre-Currency is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Substrate-Libre-Currency is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. + +use std::sync::Arc; + +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::*; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; +use serde::{Deserialize, Serialize}; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use std::{io::Write, path::PathBuf}; + +pub use pallet_distance_rpc_runtime_api::{ComputationResult, DistanceApi as DistanceRuntimeApi}; + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ComputationMetadata /*<Hash>*/ { + //pub block_hash: Hash, +} + +impl From<pallet_distance_rpc_runtime_api::ComputationMetadata> for ComputationMetadata { + fn from(_val: pallet_distance_rpc_runtime_api::ComputationMetadata) -> Self { + Self {} + } +} + +/*#[derive(Clone, Serialize, Deserialize)] +pub struct ComputationResult { + +} + +impl Into<pallet_distance_rpc_runtime_api::ComputationResult> for ComputationResult { + fn into(self) -> pallet_distance_rpc_runtime_api::ComputationResult { + pallet_distance_rpc_runtime_api::ComputationResult{} + } +}*/ + +#[rpc(client, server)] +pub trait DistanceApi<BlockHash, ResponseType> { + #[method(name = "distance_getComputationMetadata")] + fn get_computation_metadata( + &self, + at: Option<BlockHash>, + ) -> RpcResult<Option<ComputationMetadata>>; + #[method(name = "distance_publishComputationResult")] + fn publish_computation_result( + &self, + encoded_computation_result: String, //Bytes, + /*at: Option<BlockHash>,*/ + ) -> RpcResult<ResponseType>; +} + +/// Provides RPC methods to query a dispatchable's class, weight and fee. +pub struct Distance<C, P> { + /// Shared reference to the client. + client: Arc<C>, + computation_result_dir: PathBuf, + _marker: std::marker::PhantomData<P>, +} + +impl<C, P> Distance<C, P> { + /// Creates a new instance of the Distance Rpc helper. + pub fn new(client: Arc<C>, computation_result_dir: PathBuf) -> Self { + Self { + client, + computation_result_dir, + _marker: Default::default(), + } + } +} + +/// Error type of this RPC api. +pub enum Error { + /// The transaction was not decodable. + DecodeError, + /// The call to runtime failed. + RuntimeError, +} + +impl From<Error> for i32 { + fn from(e: Error) -> i32 { + match e { + Error::RuntimeError => 1, + Error::DecodeError => 2, + } + } +} + +#[async_trait] +impl<C, Block, IdtyIndex: 'static + Decode + Encode + PartialEq + Send + Sync + TypeInfo> + DistanceApiServer<<Block as BlockT>::Hash, ()> for Distance<C, (Block, IdtyIndex)> +where + Block: BlockT, + C: ProvideRuntimeApi<Block> + HeaderBackend<Block> + Send + Sync + 'static, + C::Api: DistanceRuntimeApi<Block, IdtyIndex>, +{ + fn get_computation_metadata( + &self, + at: Option<Block::Hash>, + ) -> RpcResult<Option<ComputationMetadata>> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + api.get_computation_metadata(&at).map_or_else( + |e| { + Err(CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query dispatch info.", + Some(e.to_string()), + )) + .into()) + }, + |v| Ok(v.map(Into::into)), + ) + } + fn publish_computation_result( + &self, + encoded_computation_result: String, //Bytes, + /*at: Option<Block::Hash>,*/ + ) -> RpcResult<()> { + //let api = self.client.runtime_api(); + //let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + + let encoded_computation_result = + base64::decode(encoded_computation_result).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Bad encoding.", + Some(format!("{:?}", e)), + )) + })?; + + let computation_result: ComputationResult = + Decode::decode(&mut &*encoded_computation_result).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to query dispatch info.", + Some(format!("{:?}", e)), + )) + })?; + /*api.publish_computation_result(&at, computation_result) + .map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query dispatch info.", + Some(e.to_string()), + )) + .into() + })?;*/ + std::fs::create_dir_all(&self.computation_result_dir)?; + let mut file = std::fs::File::create(self.computation_result_dir.join("result"))?; + file.write(&Encode::encode(&computation_result))?; + + Ok(()) + + //sp_distance::InherentDataProvider::from_computation_result(computation_result); + //todo!() + } +} diff --git a/pallets/distance/src/lib.rs b/pallets/distance/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..e57056d98ccc488fcf4ea1f668e4cc11ec7ebd0f --- /dev/null +++ b/pallets/distance/src/lib.rs @@ -0,0 +1,353 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// 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/>. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod median; +mod types; + +use median::*; +pub use pallet::*; +pub use types::*; + +use frame_support::traits::StorageVersion; +use pallet_authority_members::SessionIndex; +use sp_distance::{InherentError, INHERENT_IDENTIFIER}; +use sp_inherents::{InherentData, InherentIdentifier}; +use sp_std::convert::TryInto; + +type IdtyIndex = u32; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_runtime::Perbill; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); + #[pallet::config] + pub trait Config<I: 'static = ()>: + frame_system::Config + + pallet_authorship::Config + + pallet_certification::Config<I, IdtyIndex = IdtyIndex> + + pallet_identity::Config<IdtyIndex = IdtyIndex> + + pallet_session::Config + { + /// Number of sessions before starting a new computation + type FinalityLag: Get<u32>; + /// Duration of a computation (in sessions) + type ComputationMaxDelay: Get<u32>; + // /// Maximum number of identities in the evaluation queue + //type QueueSize: Get<Option<u32>>; + /// 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>; + /// Minimum ratio of accessible referees + type MinAccessibleReferees: Get<Perbill>; + } + + // STORAGE // + + /*#[pallet::storage] + pub type NextComputationMetadata<T: Config<I>, I: 'static = ()> = StorageValue< + _, + ComputationMetadata, + OptionQuery, + GetDefault, + >;*/ + + /*/// Identities to be evaluated in next sessions + #[pallet::storage] + pub type EvaluationQueue<T: Config<I>, I: 'static = ()> = StorageValue< + _, + BoundedVec<<T as pallet_certification::Config<I>>::IdtyIndex, T::MaxEvaluationsPerSession>, + ValueQuery, + >;*/ + + /*#[pallet::storage] + pub type IdentitiesBeingEvaluated<T: Config<I>, I: 'static = ()> = + StorageValue<_, Vec<<T as pallet_certification::Config<I>>::IdtyIndex>, OptionQuery>;*/ + + // /// Number of evaluators coming up with some value. + // /// Indexed by identity index, then by value. + //#[pallet::storage] + /*pub type EvaluatedIdentities<T: Config<I>, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + <T as pallet_certification::Config<I>>::IdtyIndex, + MedianAcc<Perbill>, + OptionQuery, + GetDefault, + >;*/ + /*pub type StoragePublishedResults<T: Config<I>, I: 'static = ()> = StorageValue< + _, + //PublishedResults<<T as pallet_certification::Config<I>>::IdtyIndex, T::MaxEvaluationsPerSession, T::MaxEvaluatorsPerSession>, + ( + BoundedVec< + ( + <T as pallet_certification::Config<I>>::IdtyIndex, + MedianAcc<Perbill>, + ), + T::MaxEvaluationsPerSession, + >, + BoundedVec<T::AccountId, T::MaxEvaluatorsPerSession>, + ), + ValueQuery, + >;*/ + // TODO 3 storages qu'on lit en fonction de session index % 3 + + /*/// Number of evaluators coming up with some referee count, indexed by referee count. + #[pallet::storage] + pub type Referees<T: Config<I>, I: 'static = ()> = StorageValue<_, MedianAcc<u32>, OptionQuery>;*/ + + #[pallet::storage] + pub type EvaluationPool0<T: Config<I>, I: 'static = ()> = StorageValue< + _, + ( + BoundedVec< + ( + <T as pallet_certification::Config<I>>::IdtyIndex, + MedianAcc<Perbill>, + ), + T::MaxEvaluationsPerSession, + >, + BoundedVec<T::AccountId, T::MaxEvaluatorsPerSession>, + ), + ValueQuery, + >; + #[pallet::storage] + pub type EvaluationPool1<T: Config<I>, I: 'static = ()> = StorageValue< + _, + ( + BoundedVec< + ( + <T as pallet_certification::Config<I>>::IdtyIndex, + MedianAcc<Perbill>, + ), + T::MaxEvaluationsPerSession, + >, + BoundedVec<T::AccountId, T::MaxEvaluatorsPerSession>, + ), + ValueQuery, + >; + #[pallet::storage] + pub type EvaluationPool2<T: Config<I>, I: 'static = ()> = StorageValue< + _, + ( + BoundedVec< + ( + <T as pallet_certification::Config<I>>::IdtyIndex, + MedianAcc<Perbill>, + ), + T::MaxEvaluationsPerSession, + >, + BoundedVec<T::AccountId, T::MaxEvaluatorsPerSession>, + ), + ValueQuery, + >; + + // session_index % 3: + // 0 => + // 1 => + // 2 => + + #[pallet::error] + pub enum Error<T, I = ()> { + IdentityNotInEvaluation, + IdtyNotFound, + NonEligibleForEvaluation, + QueueFull, + TooManyEvaluators, + UnauthorizedIdentity, + WrongResultLength, + } + + #[pallet::hooks] + impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {} + + // CALLS // + + #[pallet::call] + impl<T: Config<I>, I: 'static> Pallet<T, I> { + #[pallet::weight(1_000_000_000)] + pub fn evaluate_distance( + origin: OriginFor<T>, + idty_index: <T as pallet_certification::Config<I>>::IdtyIndex, + ) -> DispatchResultWithPostInfo { + let _who = ensure_signed(origin)?; + // TODO check whether origin is authorized to request evaluation + // TODO (maybe) check whether identity is already in queue + // TODO sort queue chronologically + + /*if let Some(queue_size) = <T as Config<I>>::QueueSize::get() { + ensure!( + EvaluationQueue::<T, I>::count() < queue_size, + Error::<T, I>::QueueFull + ); + }*/ + ensure!( + EvaluationQueue::<T, I>::get().len() + < <T as Config<I>>::MaxEvaluationsPerSession::get() as usize, + Error::<T, I>::QueueFull + ); + + ensure!( + pallet_identity::Identities::<T>::contains_key(idty_index), + Error::<T, I>::IdtyNotFound + ); + + EvaluationQueue::<T, I>::get() + .try_push(idty_index) + .map_err(|_| Error::<T, I>::QueueFull)?; + + Ok(().into()) + } + + #[pallet::weight(1_000_000_000)] + pub fn update_evaluation( + origin: OriginFor<T>, + computation_result: ComputationResult, /*<<T as pallet_identity::Config>::IdtyIndex>*/ + ) -> DispatchResult { + ensure_none(origin)?; + //assert!(!DidUpdate::<T>::exists(), "At most one distance computation result can be submitted per block"); + + ensure!( + computation_result.distances.len() + == StoragePublishedResults::<T, I>::get().0.len(), + Error::<T, I>::WrongResultLength + ); + + StoragePublishedResults::<T, I>::get() + .1 + .try_push( + pallet_authorship::Pallet::<T>::author() + .expect("Block author is needed to update distance evaluation"), + ) + .map_err(|_| Error::<T, I>::TooManyEvaluators)?; + + for (distance_value, (_identity, median_acc)) in computation_result + .distances + .into_iter() + .zip(StoragePublishedResults::<T, I>::get().0.iter_mut()) + { + median_acc.push(distance_value); + /*try_mutate::<_, _, Error<T, I>, _>( + identity, + |median_acc| { + median_acc + .as_mut() + .ok_or(Error::<T, I>::IdentityNotInEvaluation)? + .push(result); + Ok(()) + }, + )?;*/ + } + + Ok(()) + } + } + + // PUBLIC FUNCTIONS // + + impl<T: Config<I>, I: 'static> Pallet<T, I> { + pub fn get_computation_metadata( + ) -> Option<ComputationMetadata /*<<T as frame_system::Config>::Hash>*/> { + None + } + /*pub fn publish_computation_result( + _computation_result: ComputationResult<<T as pallet_identity::Config>::IdtyIndex>, + ) { + }*/ + } + + // INTERNAL FUNCTIONS // + + impl<T: Config<I>, I: 'static> Pallet<T, I> {} + + impl<T: Config<I>, I: 'static> pallet_authority_members::OnNewSession for Pallet<T, I> { + fn on_new_session(index: SessionIndex) -> Weight { + // TODO roll between the 3 states simultaneously + match index % 3 { + 0 => { + /*EvaluationQueue::<T, I>::drain() + .zip(0..<T as Config<I>>::MaxEvaluationsPerSession::get()) + .map(|((identity, _), _)| identity) + .for_each(|identity| { + EvaluatedIdentities::<T, I>::insert(identity, MedianAcc::new()) + });*/ + EvaluationQueue::<T, I>::get() + .into_iter() + .for_each(|identity| { + StoragePublishedResults::<T, I>::get() + .0 + .try_push((identity, MedianAcc::new())) + .expect("unreachable") + }); + } + 1 => {} + 2 => { + for (_identity, median_acc) in + StoragePublishedResults::<T, I>::get().0.into_iter() + { + if let Some(median_result) = median_acc.get_median() { + let median = match median_result { + MedianResult::One(m) => m, + MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1) + }; + if median >= T::MinAccessibleReferees::get() { + todo!(); + } else { + todo!(); + } + } + } + } + _ => unreachable!("index % 3 < 3"), + } + 0 + } + } + + #[pallet::inherent] + impl<T: Config<I>, I: 'static> ProvideInherent for Pallet<T, I> { + type Call = Call<T, I>; + type Error = InherentError; + + const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; + + fn create_inherent(data: &InherentData) -> Option<Self::Call> { + data.get_data::<ComputationResult/*<<T as pallet_identity::Config>::IdtyIndex>*/>( + &INHERENT_IDENTIFIER, + ) + .expect("Distance inherent data not correctly encoded") + .map(|inherent_data| Call::update_evaluation { + computation_result: inherent_data, + }) + } + fn is_inherent(call: &Self::Call) -> bool { + matches!(call, Self::Call::update_evaluation { .. }) + } + } +} diff --git a/pallets/distance/src/median.rs b/pallets/distance/src/median.rs new file mode 100644 index 0000000000000000000000000000000000000000..065efb3119377cea4906e86c45e176f56f1b1941 --- /dev/null +++ b/pallets/distance/src/median.rs @@ -0,0 +1,143 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// 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 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)>, + median_index: Option<u32>, + median_subindex: u32, +} + +#[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> { + pub fn new() -> Self { + Self { + samples: Vec::new(), + median_index: None, + median_subindex: 0, + } + } + + pub fn push(&mut self, sample: T) { + if let Some(median_index) = &mut self.median_index { + match self + .samples + .binary_search_by_key(&sample, |(s, _n)| s.clone()) + { + Ok(sample_index) => { + self.samples.get_mut(sample_index).expect("unreachable").1 += 1; + match (sample_index as u32).cmp(median_index) { + Ordering::Greater => { + if self.median_subindex + == self + .samples + .get(*median_index as usize) + .expect("unreachable") + .1 + * 2 + - 1 + { + self.median_subindex = 0; + *median_index += 1; + } else { + self.median_subindex += 1; + } + } + Ordering::Equal => { + self.median_subindex += 1; + } + Ordering::Less => { + if self.median_subindex == 0 { + *median_index -= 1; + self.median_subindex = self + .samples + .get(*median_index as usize) + .expect("unreachable") + .1 + * 2 + - 1; + } else { + self.median_subindex -= 1; + } + } + } + } + Err(sample_index) => { + self.samples.insert(sample_index, (sample, 1)); + if *median_index as usize >= sample_index { + if self.median_subindex == 0 { + self.median_subindex = self + .samples + .get(*median_index as usize) + .expect("unreachable") + .1 + * 2 + - 1; + } else { + self.median_subindex -= 1; + *median_index += 1; + } + } else if self.median_subindex + == self + .samples + .get(*median_index as usize) + .expect("unreachable") + .1 + * 2 + - 1 + { + self.median_subindex = 0; + *median_index += 1; + } else { + self.median_subindex += 1; + } + } + } + } else { + self.samples.push((sample, 1)); + self.median_index = Some(0); + } + } + + pub fn get_median(&self) -> Option<MedianResult<T>> { + self.median_index.map(|median_index| { + let (median_sample, median_n) = self + .samples + .get(median_index as usize) + .expect("unreachable"); + if self.median_subindex == median_n * 2 - 1 { + MedianResult::Two( + median_sample.clone(), + self.samples + .get(median_index as usize + 1) + .expect("unreachable") + .0 + .clone(), + ) + } else { + MedianResult::One(median_sample.clone()) + } + }) + } +} diff --git a/pallets/distance/src/types.rs b/pallets/distance/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..6eecd2513b6a6549ac2185d17bc3672ade37ade0 --- /dev/null +++ b/pallets/distance/src/types.rs @@ -0,0 +1,63 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// 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 crate::median::MedianAcc; + +pub use sp_distance::ComputationResult; + +use codec::{Decode, Encode}; +use frame_support::pallet_prelude::*; +use sp_inherents::IsFatalError; +//use sp_runtime::Perbill; +//use sp_std::vec::Vec; + +#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ComputationMetadata /*<Hash>*/ { + //pub block_hash: Hash, +} + +// TODO solve this! -> MaxEncodedLen +/*#[derive(Encode, Decode, TypeInfo)] +pub struct PublishedResults<IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession> { + pub distances: BoundedVec< + ( + IdtyIndex, + MedianAcc<Perbill>, + ), + MaxEvaluationsPerSession, + >, + pub evaluators: BoundedVec<IdtyIndex, MaxEvaluatorsPerSession>, +} + +impl<IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession> Default for PublishedResults<IdtyIndex, MaxEvaluationsPerSession, MaxEvaluatorsPerSession> { + fn default() -> Self { + Self { + distances: Default::default(), + evaluators: Default::default(), + } + } +}*/ + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum InherentError { + Foo, +} + +impl IsFatalError for InherentError { + fn is_fatal_error(&self) -> bool { + false + } +} diff --git a/primitives/distance/Cargo.toml b/primitives/distance/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..87f11de280e47377de47fb04c52c172f1a73965a --- /dev/null +++ b/primitives/distance/Cargo.toml @@ -0,0 +1,67 @@ +[package] +authors = ['librelois <c@elo.tf>'] +description = 'primitives for pallet distance.' +edition = "2021" +homepage = 'https://duniter.org' +license = 'AGPL-3.0' +name = 'sp-distance' +readme = 'README.md' +repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' +version = '3.0.0' + +[features] +default = ['std'] +std = [ + 'async-trait', + 'codec/std', + 'frame-support/std', + 'serde', + 'sp-inherents/std', + 'sp-runtime/std', + 'sp-std/std', + 'thiserror' +] +try-runtime = ['frame-support/try-runtime'] + +[dependencies] +async-trait = { version = "0.1.50", optional = true } +thiserror = { version = "1.0.30", optional = true } + +# substrate +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +[dependencies.codec] +default-features = false +features = ['derive'] +package = 'parity-scale-codec' +version = "3.1.5" + +[dependencies.frame-support] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.serde] +version = "1.0.101" +optional = true +features = ["derive"] + +[dependencies.sp-inherents] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-runtime] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +[dependencies.sp-std] +default-features = false +git = 'https://github.com/duniter/substrate' +branch = 'duniter-substrate-v0.9.26' + +### DOC ### + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] diff --git a/primitives/distance/src/lib.rs b/primitives/distance/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..8feeec3a0bfc9ecba6924450b4a6248060734338 --- /dev/null +++ b/primitives/distance/src/lib.rs @@ -0,0 +1,126 @@ +// Copyright 2022 Axiom-Team +// +// This file is part of Duniter-v2S. +// +// Duniter-v2S is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// Duniter-v2S is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// 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/>. + +//! Defines types and traits for users of pallet distance. + +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::type_complexity)] + +use codec::{Decode, Encode}; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; +//#[cfg(feature = "std")] +//use serde::{Deserialize, Serialize}; +use sp_inherents::{InherentData, InherentIdentifier, IsFatalError}; +use sp_runtime::Perbill; +use sp_std::vec::Vec; +#[cfg(feature = "std")] +use std::{marker::PhantomData, path::PathBuf}; + +pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"distanc0"; + +#[derive(Clone, Decode, Encode, PartialEq, RuntimeDebug, TypeInfo)] +pub struct ComputationResult /*<IdtyIndex: Decode + Encode + PartialEq + TypeInfo>*/ { + pub distances: Vec<Perbill>, +} + +/*pub enum Event<IdtyId, MetaData = ()> { +}*/ + +#[derive(Encode, sp_runtime::RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Decode, thiserror::Error))] +pub enum InherentError { + #[cfg_attr(feature = "std", error("InvalidComputationResultFile"))] + InvalidComputationResultFile, +} + +impl IsFatalError for InherentError { + fn is_fatal_error(&self) -> bool { + match self { + InherentError::InvalidComputationResultFile => false, + } + } +} + +impl InherentError { + #[cfg(feature = "std")] + pub fn try_from(id: &InherentIdentifier, mut data: &[u8]) -> Option<Self> { + if id == &INHERENT_IDENTIFIER { + <InherentError as codec::Decode>::decode(&mut data).ok() + } else { + None + } + } +} + +#[cfg(feature = "std")] +pub struct InherentDataProvider<IdtyIndex: Decode + Encode + PartialEq + TypeInfo> { + //computation_result: ComputationResult<IdtyIndex>, + computation_result_dir: PathBuf, + //result_expected_for_round: Option<u32>, // TODO (None => soit 2 premiers rounds, soit déjà publié) + _p: PhantomData<IdtyIndex>, +} + +#[cfg(feature = "std")] +impl<IdtyIndex: Decode + Encode + PartialEq + TypeInfo> InherentDataProvider<IdtyIndex> { + /*pub fn from_computation_result(computation_result: ComputationResult<IdtyIndex>, computation_result_dir: PathBuf) -> Self { + Self { computation_result, computation_result_dir } + }*/ + pub fn new(computation_result_dir: PathBuf) -> Self { + Self { + computation_result_dir, + _p: PhantomData, + } + } +} + +#[cfg(feature = "std")] +#[async_trait::async_trait] +impl<IdtyIndex: Decode + Encode + PartialEq + TypeInfo + Send + Sync> + sp_inherents::InherentDataProvider for InherentDataProvider<IdtyIndex> +{ + fn provide_inherent_data( + &self, + inherent_data: &mut InherentData, + ) -> Result<(), sp_inherents::Error> { + // TODO check whether to do something: check if round corresponds between file and self.expected_round + + // TODO match error + if let Ok(file) = std::fs::File::open(self.computation_result_dir.join("result")) { + let mut reader = codec::IoReader(file); + // TODO remove unwrap + inherent_data.put_data( + INHERENT_IDENTIFIER, + &ComputationResult::decode(&mut reader).unwrap(), + )?; + } + Ok(()) + } + + async fn try_handle_error( + &self, + identifier: &InherentIdentifier, + error: &[u8], + ) -> Option<Result<(), sp_inherents::Error>> { + if *identifier != INHERENT_IDENTIFIER { + return None; + } + + match InherentError::try_from(&INHERENT_IDENTIFIER, error)? { + o => Some(Err(sp_inherents::Error::Application(Box::from(o)))), + } + } +} diff --git a/resources/metadata.scale b/resources/metadata.scale index ff4c1c40a4d58bf8b3d8dc74373830583ef1ddbb..0b6f620736c233764ab5453fc420a8f6b522b4d5 100644 Binary files a/resources/metadata.scale and b/resources/metadata.scale differ diff --git a/runtime/common/Cargo.toml b/runtime/common/Cargo.toml index a4685b21bdeb8eefd696b83106d3ad4beb53afd9..2adb84d554483e5ff406f0aa344b4ce79274e6fb 100644 --- a/runtime/common/Cargo.toml +++ b/runtime/common/Cargo.toml @@ -33,6 +33,8 @@ std = [ 'pallet-babe/std', 'pallet-balances/std', 'pallet-certification/std', + 'pallet-distance/std', + 'pallet-distance-rpc-runtime-api/std', 'pallet-duniter-account/std', 'pallet-duniter-wot/std', 'pallet-grandpa/std', @@ -50,6 +52,7 @@ std = [ "serde_derive", 'sp-arithmetic/std', 'sp-core/std', + 'sp-distance/std', 'sp-membership/std', 'sp-runtime/std', 'sp-std/std', @@ -65,6 +68,8 @@ try-runtime = [ duniter-primitives = { path = '../../primitives/duniter', default-features = false } pallet-authority-members = { path = '../../pallets/authority-members', default-features = false } pallet-certification = { path = '../../pallets/certification', default-features = false } +pallet-distance = { path = "../../pallets/distance", default-features = false } +pallet-distance-rpc-runtime-api = { path = "../../pallets/distance/rpc/runtime-api", default-features = false } pallet-duniter-account = { path = '../../pallets/duniter-account', default-features = false } pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false } pallet-identity = { path = '../../pallets/identity', default-features = false } @@ -73,6 +78,7 @@ pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-featu pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } +sp-distance = { path = '../../primitives/distance', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } # Crates.io diff --git a/runtime/common/src/apis.rs b/runtime/common/src/apis.rs index dcd38e6e10ea4dccbea315532a2763c162757144..de510f739b751ba2313f26a837f9802a807f1d5c 100644 --- a/runtime/common/src/apis.rs +++ b/runtime/common/src/apis.rs @@ -208,6 +208,14 @@ macro_rules! runtime_apis { TransactionPayment::query_fee_details(uxt, len) } } + impl pallet_distance_rpc_runtime_api::DistanceApi<Block, u32> for Runtime { + fn get_computation_metadata() -> Option<pallet_distance_rpc_runtime_api::ComputationMetadata/*<<T as frame_system::Config>::Hash>*/> { + Distance::get_computation_metadata() + } + /*fn publish_computation_result(computation_result: pallet_distance_rpc_runtime_api::ComputationResult<u32>) { + //Distance::publish_computation_result(computation_result) + }*/ + } #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime<Block> for Runtime { diff --git a/runtime/common/src/handlers.rs b/runtime/common/src/handlers.rs index e09b1d30fd008f604e4834e9f4b8a0c3531be31a..edf25c2943f4c2037572e45cd95c1334bc535f08 100644 --- a/runtime/common/src/handlers.rs +++ b/runtime/common/src/handlers.rs @@ -27,11 +27,11 @@ use sp_runtime::traits::IsMember; pub struct OnNewSessionHandler<Runtime>(core::marker::PhantomData<Runtime>); impl<Runtime> pallet_authority_members::traits::OnNewSession for OnNewSessionHandler<Runtime> where - Runtime: pallet_provide_randomness::Config, + Runtime: pallet_provide_randomness::Config + pallet_distance::Config<Instance1>, { - fn on_new_session(_index: sp_staking::SessionIndex) -> Weight { + fn on_new_session(index: sp_staking::SessionIndex) -> Weight { pallet_provide_randomness::Pallet::<Runtime>::on_new_epoch(); - 0 + pallet_distance::Pallet::<Runtime, Instance1>::on_new_session(index) } } diff --git a/runtime/common/src/pallets_config.rs b/runtime/common/src/pallets_config.rs index 974db7fa974d19b6e7eed3bec498457852222a02..eb3fb4222825c149375d412ef6f91e426a743aed 100644 --- a/runtime/common/src/pallets_config.rs +++ b/runtime/common/src/pallets_config.rs @@ -466,6 +466,18 @@ macro_rules! pallets_config { type OnRemovedCert = Wot; type ValidityPeriod = ValidityPeriod; } + parameter_types! { + pub const MinAccessibleReferees: Perbill = Perbill::from_percent(80); + } + impl pallet_distance::Config<Instance1> for Runtime { + //type IdtyIndex: IdtyIndex; + type FinalityLag = frame_support::traits::ConstU32<1>; + type ComputationMaxDelay = frame_support::traits::ConstU32<1>; + //type QueueSize = frame_support::traits::ConstU32<800>; + type MaxEvaluationsPerSession = frame_support::traits::ConstU32<100>; + type MaxEvaluatorsPerSession = frame_support::traits::ConstU32<100>;// TODO <= number of blocks per session + type MinAccessibleReferees = MinAccessibleReferees; + } // SMITHS SUB-WOT // diff --git a/runtime/gdev/Cargo.toml b/runtime/gdev/Cargo.toml index 65d3ec9afbf49d90c96ddf69b9189a25d293207c..d01c15dd7f667fbafdc54596ff20d1be2d56c9c4 100644 --- a/runtime/gdev/Cargo.toml +++ b/runtime/gdev/Cargo.toml @@ -63,6 +63,8 @@ std = [ 'pallet-balances/std', 'pallet-certification/std', 'pallet-collective/std', + 'pallet-distance/std', + 'pallet-distance-rpc-runtime-api/std', 'pallet-duniter-test-parameters/std', 'pallet-duniter-account/std', 'pallet-duniter-wot/std', @@ -94,6 +96,7 @@ std = [ 'sp-block-builder/std', 'sp-consensus-babe/std', 'sp-core/std', + 'sp-distance/std', 'sp-inherents/std', 'sp-offchain/std', 'sp-membership/std', @@ -136,6 +139,8 @@ sp-keyring = { git = 'https://github.com/duniter/substrate', branch = 'duniter-s common-runtime = { path = "../common", default-features = false } pallet-authority-members = { path = '../../pallets/authority-members', default-features = false } pallet-certification = { path = '../../pallets/certification', default-features = false } +pallet-distance = { path = "../../pallets/distance", default-features = false } +pallet-distance-rpc-runtime-api = { path = "../../pallets/distance/rpc/runtime-api", default-features = false } pallet-duniter-test-parameters = { path = '../../pallets/duniter-test-parameters', default-features = false } pallet-duniter-account = { path = '../../pallets/duniter-account', default-features = false } pallet-duniter-wot = { path = '../../pallets/duniter-wot', default-features = false } @@ -145,6 +150,7 @@ pallet-oneshot-account = { path = '../../pallets/oneshot-account', default-featu pallet-provide-randomness = { path = '../../pallets/provide-randomness', default-features = false } pallet-universal-dividend = { path = '../../pallets/universal-dividend', default-features = false } pallet-upgrade-origin = { path = '../../pallets/upgrade-origin', default-features = false } +sp-distance = { path = '../../primitives/distance', default-features = false } sp-membership = { path = '../../primitives/membership', default-features = false } # crates.io diff --git a/runtime/gdev/src/lib.rs b/runtime/gdev/src/lib.rs index 33e172eb2b0e88afb816ec25a07d95d32ff5dbb1..8e927c407ba0dd97db81113348fe009201fe01d5 100644 --- a/runtime/gdev/src/lib.rs +++ b/runtime/gdev/src/lib.rs @@ -313,6 +313,7 @@ construct_runtime!( Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>} = 41, Membership: pallet_membership::<Instance1>::{Pallet, Call, Config<T>, Storage, Event<T>} = 42, Cert: pallet_certification::<Instance1>::{Pallet, Call, Config<T>, Storage, Event<T>} = 43, + Distance: pallet_distance::<Instance1>::{Pallet, Call, Storage} = 44, // Smiths Sub-Wot SmithsSubWot: pallet_duniter_wot::<Instance2>::{Pallet} = 50,