From ebc4730cd76d1fdf413a873c28d634994d044b18 Mon Sep 17 00:00:00 2001 From: Benjamin Gallois <business@gallois.cc> Date: Fri, 29 Nov 2024 17:10:22 +0100 Subject: [PATCH] Fix #262 distance oracle pool index (nodes/rust/duniter-v2s!290) * update xtask * reference rust doc in readme * review fix * add log for distance oracle in main binary * add oracle file versioning * add docs * add files versionning * fix after rebase * update crate docs * refactor oracle * fix missing log arg * fix #262 --- client/distance/README.md | 3 + client/distance/src/lib.rs | 50 ++++-- distance-oracle/Cargo.toml | 1 - distance-oracle/README.md | 30 +--- distance-oracle/src/api.rs | 11 +- distance-oracle/src/lib.rs | 214 ++++++++++++++++++------- distance-oracle/src/main.rs | 4 +- distance-oracle/src/mock.rs | 8 +- distance-oracle/src/tests.rs | 2 +- end2end-tests/tests/common/distance.rs | 18 +-- node/README.md | 3 + node/src/cli.rs | 8 +- node/src/command.rs | 7 +- node/src/lib.rs | 48 ++++++ pallets/README.md | 28 ++-- pallets/distance/src/benchmarking.rs | 8 +- pallets/distance/src/lib.rs | 18 +-- resources/metadata.scale | Bin 149644 -> 149648 bytes runtime/README.md | 6 +- xtask/src/gen_doc.rs | 1 + 20 files changed, 317 insertions(+), 151 deletions(-) create mode 100644 client/distance/README.md create mode 100644 node/README.md diff --git a/client/distance/README.md b/client/distance/README.md new file mode 100644 index 000000000..dff4c144c --- /dev/null +++ b/client/distance/README.md @@ -0,0 +1,3 @@ +# Distance Oracle Inherent Data Provider + +You can find the autogenerated documentation at: [https://doc-duniter-org.ipns.pagu.re/dc_distance/index.html](https://doc-duniter-org.ipns.pagu.re/dc_distance/index.html). diff --git a/client/distance/src/lib.rs b/client/distance/src/lib.rs index 19c3fe661..c36091612 100644 --- a/client/distance/src/lib.rs +++ b/client/distance/src/lib.rs @@ -14,11 +14,37 @@ // 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/>. +//! # Distance Oracle Inherent Data Provider +//! +//! This crate provides functionality for creating an **inherent data provider** +//! specifically designed for the "Distance Oracle". +//! The inherent data provider is responsible for fetching and delivering +//! computation results required for the runtime to process distance evaluations. +//! +//! ## Relationship with Distance Oracle +//! +//! The **distance-oracle** is responsible for computing distance evaluations, +//! storing the results to be read in the next period, and saving them to files. +//! These files are then read by **this inherent data provider** +//! to provide the required data to the runtime. +//! +//! ## Overview +//! +//! - Retrieves **period index** and **evaluation results** from the storage and file system. +//! - Determines whether the computation results for the current period have already been published. +//! - Reads and parses evaluation result files when available, providing the necessary data to the runtime. + use frame_support::pallet_prelude::*; use sc_client_api::{ProvideUncles, StorageKey, StorageProvider}; use sp_runtime::{generic::BlockId, traits::Block as BlockT, AccountId32}; use std::path::PathBuf; +/// The file version that should match the distance oracle one. +/// This ensures that the smith avoids accidentally submitting invalid data +/// in case there are changes in logic between the runtime and the oracle, +/// thereby preventing potential penalties. +const VERSION_PREFIX: &str = "001-"; + type IdtyIndex = u32; #[derive(Debug, thiserror::Error)] @@ -40,19 +66,19 @@ where Backend: sc_client_api::Backend<B>, IdtyIndex: Decode + Encode + PartialEq + TypeInfo, { - // Retrieve the pool_index from storage. If storage is inaccessible or the data is corrupted, + // Retrieve the period_index from storage. If storage is inaccessible or the data is corrupted, // return the appropriate error. - let pool_index = client + let period_index = client .storage( parent, &StorageKey( - frame_support::storage::storage_prefix(b"Distance", b"CurrentPoolIndex").to_vec(), + frame_support::storage::storage_prefix(b"Distance", b"CurrentPeriodIndex").to_vec(), ), )? .map_or_else( || { Err(sc_client_api::blockchain::Error::Storage( - "CurrentPoolIndex value not found".to_string(), + "CurrentPeriodIndex value not found".to_string(), )) }, |raw| { @@ -70,7 +96,7 @@ where &StorageKey( frame_support::storage::storage_prefix( b"Distance", - match pool_index { + match period_index % 3 { 0 => b"EvaluationPool0", 1 => b"EvaluationPool1", 2 => b"EvaluationPool2", @@ -117,18 +143,19 @@ where } } - // Read evaluation result from file, if it exists, then remove it + // Read evaluation result from file, if it exists log::debug!( "🧙 [distance oracle] Reading evaluation result from file {:?}", - distance_dir.clone().join(pool_index.to_string()) + distance_dir.clone().join(period_index.to_string()) ); - let evaluation_result_file_path = distance_dir.join(pool_index.to_string()); - let evaluation_result = match std::fs::read(&evaluation_result_file_path) { + let evaluation_result = match std::fs::read( + distance_dir.join(VERSION_PREFIX.to_owned() + &period_index.to_string()), + ) { Ok(data) => data, Err(e) => { match e.kind() { std::io::ErrorKind::NotFound => { - log::debug!("🧙 [distance oracle] Evaluation result file not found"); + log::debug!("🧙 [distance oracle] Evaluation result file not found. Please ensure that the oracle version matches {}", VERSION_PREFIX); } _ => { log::error!( @@ -139,9 +166,6 @@ where return Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(None)); } }; - std::fs::remove_file(&evaluation_result_file_path).unwrap_or_else(move |e| { - log::warn!("🧙 [distance oracle] Cannot remove old result file `{evaluation_result_file_path:?}`: {e:?}") - }); log::info!("🧙 [distance oracle] Providing evaluation result"); Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(Some( diff --git a/distance-oracle/Cargo.toml b/distance-oracle/Cargo.toml index 60869ed6f..2be78ee9c 100644 --- a/distance-oracle/Cargo.toml +++ b/distance-oracle/Cargo.toml @@ -23,7 +23,6 @@ std = [ "sp-runtime/std", ] try-runtime = ["sp-distance/try-runtime", "sp-runtime/try-runtime"] -runtime-benchmarks = [] [dependencies] clap = { workspace = true, features = ["derive"], optional = true } diff --git a/distance-oracle/README.md b/distance-oracle/README.md index cff2d0546..169549017 100644 --- a/distance-oracle/README.md +++ b/distance-oracle/README.md @@ -1,29 +1,3 @@ -# Distance oracle +# Distance Oracle -> for explanation about the Duniter web of trust, see https://duniter.org/wiki/web-of-trust/deep-dive-wot/ - -Distance computation on the Duniter web of trust is an expensive operation that should not be included in the runtime for multiple reasons: - -- it could exceed the time available for a block computation -- it takes a lot of resource from the host machine -- the result is not critical to the operation of Äž1 - -It is then separated into an other program that the user (a duniter smith) can choose to run or not. This program publishes its result in a inherent and the network selects the median of the results given by the smith who published some. - -## Structure - -This feature is organized in multiple parts: - -- **/distance-oracle/** (here): binary executing the distance algorithm -- **/primitives/distance/**: primitive types used both by client and runtime -- **/client/distance/**: exposes the `create_distance_inherent_data_provider` which provides data to the runtime -- **/pallets/distance/**: distance pallet exposing type, traits, storage/calls/hooks executing in the runtime - -## Usage (with Docker) - -See [docker-compose.yml](../docker-compose.yml) for an example of how to run the distance oracle with Docker. - -Output: - - 2023-12-09T14:45:05.942Z INFO [distance_oracle] Nothing to do: Pool does not exist - Waiting 1800 seconds before next execution... \ No newline at end of file +You can find the autogenerated documentation at: [https://doc-duniter-org.ipns.pagu.re/distance_oracle/index.html](https://doc-duniter-org.ipns.pagu.re/distance_oracle/index.html). diff --git a/distance-oracle/src/api.rs b/distance-oracle/src/api.rs index 16f98a61b..9e507dd03 100644 --- a/distance-oracle/src/api.rs +++ b/distance-oracle/src/api.rs @@ -19,11 +19,12 @@ use crate::runtime; use log::debug; -use subxt::utils::H256; - pub type Client = subxt::OnlineClient<crate::RuntimeConfig>; pub type AccountId = subxt::utils::AccountId32; pub type IdtyIndex = u32; +pub type EvaluationPool = + runtime::runtime_types::pallet_distance::types::EvaluationPool<AccountId, IdtyIndex>; +pub type H256 = subxt::utils::H256; pub async fn client(rpc_url: impl AsRef<str>) -> Client { Client::from_insecure_url(rpc_url) @@ -40,11 +41,11 @@ pub async fn parent_hash(client: &Client) -> H256 { .hash() } -pub async fn current_pool_index(client: &Client, parent_hash: H256) -> u32 { +pub async fn current_period_index(client: &Client, parent_hash: H256) -> u32 { client .storage() .at(parent_hash) - .fetch(&runtime::storage().distance().current_pool_index()) + .fetch(&runtime::storage().distance().current_period_index()) .await .expect("Cannot fetch current pool index") .unwrap_or_default() @@ -54,7 +55,7 @@ pub async fn current_pool( client: &Client, parent_hash: H256, current_pool_index: u32, -) -> Option<runtime::runtime_types::pallet_distance::types::EvaluationPool<AccountId, IdtyIndex>> { +) -> Option<EvaluationPool> { client .storage() .at(parent_hash) diff --git a/distance-oracle/src/lib.rs b/distance-oracle/src/lib.rs index 89a4fa966..a98089727 100644 --- a/distance-oracle/src/lib.rs +++ b/distance-oracle/src/lib.rs @@ -14,6 +14,38 @@ // 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/>. +//! # Distance Oracle +//! +//! The **Distance Oracle** is a standalone program designed to calculate the distances between identities in the Duniter Web of Trust (WoT). This process is computationally intensive and is therefore decoupled from the main runtime. It allows smith users to choose whether to run the oracle and provide results to the network. +//! +//! The **oracle** works in conjunction with the **Inherent Data Provider** and the **Distance Pallet** in the runtime to deliver periodic computation results. The **Inherent Data Provider** fetches and supplies these results to the runtime, ensuring that the necessary data for distance evaluations is available to be processed at the appropriate time in the runtime lifecycle. +//! +//! ## Structure +//! +//! The Distance Oracle is organized into the following modules: +//! +//! 1. **`/distance-oracle/`**: Contains the main binary for executing the distance computation. +//! 2. **`/primitives/distance/`**: Defines primitive types shared between the client and runtime. +//! 3. **`/client/distance/`**: Exposes the `create_distance_inherent_data_provider`, which feeds data into the runtime through the Inherent Data Provider. +//! 4. **`/pallets/distance/`**: A pallet that handles distance-related types, traits, storage, and hooks in the runtime, coordinating the interaction between the oracle, inherent data provider, and runtime. +//! +//! ## How it works +//! - The **Distance Pallet** adds an evaluation request at period `i` in the runtime. +//! - The **Distance Oracle** evaluates this request at period `i + 1`, computes the necessary results and stores them on disk. +//! - The **Inherent Data Provider** reads this evaluation result from disk at period `i + 2` and provides it to the runtime to perform the required operations. +//! +//! ## Usage +//! +//! ### Docker Integration +//! +//! To run the Distance Oracle, use the provided Docker setup. Refer to the [docker-compose.yml](../docker-compose.yml) file for an example configuration. +//! +//! Example Output: +//! ```text +//! 2023-12-09T14:45:05.942Z INFO [distance_oracle] Nothing to do: Pool does not exist +//! Waiting 1800 seconds before next execution... +//! ``` + #[cfg(not(test))] pub mod api; #[cfg(test)] @@ -24,15 +56,20 @@ mod tests; #[cfg(test)] pub use mock as api; -use api::{AccountId, IdtyIndex}; +use api::{AccountId, EvaluationPool, IdtyIndex, H256}; use codec::Encode; use fnv::{FnvHashMap, FnvHashSet}; -use log::{debug, error, info}; +use log::{debug, info, warn}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use std::{io::Write, path::PathBuf}; -// TODO select metadata file using features +/// The file version must match the version used by the inherent data provider. +/// This ensures that the smith avoids accidentally submitting invalid data +/// in case there are changes in logic between the runtime and the oracle, +/// thereby preventing potential penalties. +const VERSION_PREFIX: &str = "001-"; + #[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")] pub mod runtime {} @@ -83,10 +120,18 @@ impl Default for Settings { } } -/// Asynchronously runs a computation using the provided client and saves the result to a file. -pub async fn run_and_save(client: &api::Client, settings: &Settings) { - let Some((evaluation, _current_pool_index, evaluation_result_path)) = - run(client, settings, true).await +/// Runs the evaluation process, saves the results, and cleans up old files. +/// +/// This function performs the following steps: +/// 1. Runs the evaluation task by invoking `compute_distance_evaluation`, which provides: +/// - The evaluation results. +/// - The current period index. +/// - The file path where the results should be stored. +/// 2. Saves the evaluation results to a file in the specified directory. +/// 3. Cleans up outdated evaluation files. +pub async fn run(client: &api::Client, settings: &Settings) { + let Some((evaluation, current_period_index, evaluation_result_path)) = + compute_distance_evaluation(client, settings).await else { return; }; @@ -113,58 +158,53 @@ pub async fn run_and_save(client: &api::Client, settings: &Settings) { "Cannot write distance evaluation result to file `{evaluation_result_path:?}`: {e:?}" ) }); + + // When a new result is written, remove old results except for the current period used by the inherent logic and the next period that was just generated. + settings + .evaluation_result_dir + .read_dir() + .unwrap_or_else(|e| { + panic!( + "Cannot read distance evaluation result directory `{:?}`: {:?}", + settings.evaluation_result_dir, e + ) + }) + .flatten() + .filter_map(|entry| { + entry + .file_name() + .to_str() + .and_then(|name| { + name.split('-').last()?.parse::<u32>().ok().filter(|&pool| { + pool != current_period_index && pool != current_period_index + 1 + }) + }) + .map(|_| entry.path()) + }) + .for_each(|path| { + std::fs::remove_file(&path) + .unwrap_or_else(|e| warn!("Cannot remove file `{:?}`: {:?}", path, e)); + }); } -/// Asynchronously runs a computation based on the provided client and settings. -/// Returns `Option<(evaluation, current_pool_index, evaluation_result_path)>`. -pub async fn run( +/// Evaluates distance for the current period and prepares results for storage. +/// +/// This function performs the following steps: +/// 1. Prepares the evaluation context using `prepare_evaluation_context`. If the context is not +/// ready (e.g., no pending evaluations, or results already exist), it returns `None`. +/// 2. Evaluates distances for all identities in the evaluation pool. +/// 3. Returns the evaluation results, the current period index, and the path to store the results. +/// +pub async fn compute_distance_evaluation( client: &api::Client, settings: &Settings, - handle_fs: bool, ) -> Option<(Vec<sp_runtime::Perbill>, u32, PathBuf)> { - let parent_hash = api::parent_hash(client).await; - - let max_depth = api::max_referee_distance(client).await; - - let current_pool_index = api::current_pool_index(client, parent_hash).await; + let (evaluation_block, current_period_index, evaluation_pool, evaluation_result_path) = + prepare_evaluation_context(client, settings).await?; - // Fetch the pending identities - let Some(evaluation_pool) = api::current_pool(client, parent_hash, current_pool_index).await - else { - info!("Nothing to do: Pool does not exist"); - return None; - }; - - // Stop if nothing to evaluate - if evaluation_pool.evaluations.0.is_empty() { - info!("Nothing to do: Pool is empty"); - return None; - } - - let evaluation_result_path = settings - .evaluation_result_dir - .join(((current_pool_index + 1) % 3).to_string()); - - if handle_fs { - // Stop if already evaluated - if evaluation_result_path - .try_exists() - .expect("Result path unavailable") - { - info!("Nothing to do: File already exists"); - return None; - } - - std::fs::create_dir_all(&settings.evaluation_result_dir).unwrap_or_else(|e| { - error!( - "Cannot create distance evaluation result directory `{0:?}`: {e:?}", - settings.evaluation_result_dir - ); - }); - } + info!("Evaluating distance for period {}", current_period_index); - info!("Evaluating distance for pool {}", current_pool_index); - let evaluation_block = api::evaluation_block(client, parent_hash).await; + let max_depth = api::max_referee_distance(client).await; // member idty -> issued certs let mut members = FnvHashMap::<IdtyIndex, u32>::default(); @@ -219,9 +259,75 @@ pub async fn run( .map(|(idty, _)| distance_rule(&received_certs, &referees, max_depth, *idty)) .collect(); - Some((evaluation, current_pool_index, evaluation_result_path)) + Some((evaluation, current_period_index, evaluation_result_path)) +} + +/// Prepares the context for the next evaluation task. +/// +/// This function performs the following steps: +/// 1. Fetches the parent hash of the latest block from the API. +/// 2. Determines the current period index. +/// 3. Retrieves the evaluation pool for the current period. +/// - If the pool does not exist or is empty, it returns `None`. +/// 4. Checks if the evaluation result file for the next period already exists. +/// - If it exists, the task has already been completed, so the function returns `None`. +/// 5. Ensures the evaluation result directory is available, creating it if necessary. +/// 6. Retrieves the block number of the evaluation. +/// +async fn prepare_evaluation_context( + client: &api::Client, + settings: &Settings, +) -> Option<(H256, u32, EvaluationPool, PathBuf)> { + let parent_hash = api::parent_hash(client).await; + + let current_period_index = api::current_period_index(client, parent_hash).await; + + // Fetch the pending identities + let Some(evaluation_pool) = + api::current_pool(client, parent_hash, current_period_index % 3).await + else { + info!("Nothing to do: Pool does not exist"); + return None; + }; + + // Stop if nothing to evaluate + if evaluation_pool.evaluations.0.is_empty() { + info!("Nothing to do: Pool is empty"); + return None; + } + + // The result is saved in a file named `current_period_index + 1`. + // It will be picked up during the next period by the inherent. + let evaluation_result_path = settings + .evaluation_result_dir + .join(VERSION_PREFIX.to_owned() + &(current_period_index + 1).to_string()); + + // Stop if already evaluated + if evaluation_result_path + .try_exists() + .expect("Result path unavailable") + { + info!("Nothing to do: File already exists"); + return None; + } + + #[cfg(not(test))] + std::fs::create_dir_all(&settings.evaluation_result_dir).unwrap_or_else(|e| { + panic!( + "Cannot create distance evaluation result directory `{0:?}`: {e:?}", + settings.evaluation_result_dir + ); + }); + + Some(( + api::evaluation_block(client, parent_hash).await, + current_period_index, + evaluation_pool, + evaluation_result_path, + )) } +/// Recursively explores the certification graph to identify referees accessible within a given depth. fn distance_rule_recursive( received_certs: &FnvHashMap<IdtyIndex, Vec<IdtyIndex>>, referees: &FnvHashMap<IdtyIndex, u32>, @@ -267,7 +373,7 @@ fn distance_rule_recursive( } } -/// Returns the fraction `nb_accessible_referees / nb_referees` +/// Calculates the fraction of accessible referees to total referees for a given identity. fn distance_rule( received_certs: &FnvHashMap<IdtyIndex, Vec<IdtyIndex>>, referees: &FnvHashMap<IdtyIndex, u32>, diff --git a/distance-oracle/src/main.rs b/distance-oracle/src/main.rs index aaa580fe3..d91584141 100644 --- a/distance-oracle/src/main.rs +++ b/distance-oracle/src/main.rs @@ -51,10 +51,10 @@ async fn main() { let mut interval = tokio::time::interval(std::time::Duration::from_secs(duration)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); loop { - distance_oracle::run_and_save(&client, &settings).await; + distance_oracle::run(&client, &settings).await; interval.tick().await; } } else { - distance_oracle::run_and_save(&client, &settings).await; + distance_oracle::run(&client, &settings).await; } } diff --git a/distance-oracle/src/mock.rs b/distance-oracle/src/mock.rs index f5de146c6..881022205 100644 --- a/distance-oracle/src/mock.rs +++ b/distance-oracle/src/mock.rs @@ -19,7 +19,6 @@ use crate::runtime::runtime_types::{ }; use dubp_wot::{data::rusty::RustyWebOfTrust, WebOfTrust, WotId}; -use sp_core::H256; use std::collections::BTreeSet; pub struct Client { @@ -28,8 +27,9 @@ pub struct Client { } pub type AccountId = subxt::ext::sp_runtime::AccountId32; pub type IdtyIndex = u32; +pub type H256 = subxt::utils::H256; -pub struct EvaluationPool<AccountId: Ord, IdtyIndex> { +pub struct EvaluationPool { pub evaluations: (Vec<(IdtyIndex, MedianAcc<Perbill>)>,), pub evaluators: BTreeSet<AccountId>, } @@ -46,7 +46,7 @@ pub async fn parent_hash(_client: &Client) -> H256 { Default::default() } -pub async fn current_pool_index(_client: &Client, _parent_hash: H256) -> u32 { +pub async fn current_period_index(_client: &Client, _parent_hash: H256) -> u32 { 0 } @@ -54,7 +54,7 @@ pub async fn current_pool( client: &Client, _parent_hash: H256, _current_session: u32, -) -> Option<EvaluationPool<AccountId, IdtyIndex>> { +) -> Option<EvaluationPool> { Some(EvaluationPool { evaluations: (client .wot diff --git a/distance-oracle/src/tests.rs b/distance-oracle/src/tests.rs index a1c197fa8..23df3f287 100644 --- a/distance-oracle/src/tests.rs +++ b/distance-oracle/src/tests.rs @@ -58,7 +58,7 @@ async fn test_distance_against_v1() { client.pool_len = n; let t_a = std::time::Instant::now(); - let results = crate::run(&client, &Default::default(), false) + let results = crate::compute_distance_evaluation(&client, &Default::default()) .await .unwrap(); println!("new time: {}", t_a.elapsed().as_millis()); diff --git a/end2end-tests/tests/common/distance.rs b/end2end-tests/tests/common/distance.rs index f9f43d670..f7d02df55 100644 --- a/end2end-tests/tests/common/distance.rs +++ b/end2end-tests/tests/common/distance.rs @@ -51,15 +51,15 @@ pub async fn run_oracle( 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::api::client(rpc_url.clone()).await, - &distance_oracle::Settings { - evaluation_result_dir: PathBuf::default(), - rpc_url, - }, - false, - ) - .await + if let Some((distances, _current_session, _evaluation_result_path)) = + distance_oracle::compute_distance_evaluation( + &distance_oracle::api::client(rpc_url.clone()).await, + &distance_oracle::Settings { + evaluation_result_dir: PathBuf::default(), + rpc_url, + }, + ) + .await { // Distance evaluation period is 7 blocks for _ in 0..7 { diff --git a/node/README.md b/node/README.md new file mode 100644 index 000000000..a365610b2 --- /dev/null +++ b/node/README.md @@ -0,0 +1,3 @@ +# Duniter Node + +You can find the autogenerated documentation at: [https://doc-duniter-org.ipns.pagu.re/duniter/index.html](https://doc-duniter-org.ipns.pagu.re/duniter/index.html). diff --git a/node/src/cli.rs b/node/src/cli.rs index 196e29d0f..4a29ae0c0 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -132,12 +132,16 @@ pub struct Completion { #[cfg(feature = "distance-oracle")] #[derive(Debug, clap::Parser)] pub struct DistanceOracle { + /// Saving path. #[clap(short = 'd', long, default_value = "/tmp/duniter/chains/gdev/distance")] pub evaluation_result_dir: String, - /// Number of seconds between two evaluations (oneshot if absent) + /// Number of seconds between two evaluations (oneshot if absent). #[clap(short = 'i', long)] pub interval: Option<u64>, - /// Node used for fetching state + /// Node used for fetching state. #[clap(short = 'u', long, default_value = "ws://127.0.0.1:9944")] pub rpc_url: String, + /// Sets the logging level (e.g., debug, error, info, trace, warn). + #[clap(short = 'l', long, default_value = "info")] + pub log: String, } diff --git a/node/src/command.rs b/node/src/command.rs index 9aa124d91..58eabb014 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -285,6 +285,9 @@ pub fn run() -> sc_cli::Result<()> { } #[cfg(feature = "distance-oracle")] Some(Subcommand::DistanceOracle(cmd)) => sc_cli::build_runtime()?.block_on(async move { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_profiling(sc_cli::TracingReceiver::Log.into(), cmd.log.clone()); + builder.init()?; let client = distance_oracle::api::client(&cmd.rpc_url).await; let settings = distance_oracle::Settings { @@ -296,11 +299,11 @@ pub fn run() -> sc_cli::Result<()> { let mut interval = tokio::time::interval(std::time::Duration::from_secs(duration)); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); loop { - distance_oracle::run_and_save(&client, &settings).await; + distance_oracle::run(&client, &settings).await; interval.tick().await; } } else { - distance_oracle::run_and_save(&client, &settings).await; + distance_oracle::run(&client, &settings).await; } Ok(()) }), diff --git a/node/src/lib.rs b/node/src/lib.rs index caf6e6f32..2e5574300 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -14,6 +14,54 @@ // 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/>. +//! # Duniter v2s Documentation +//! +//! 🆙 A rewriting of [Duniter v1](https://duniter.org) in the [Substrate](https://www.substrate.io/) framework. +//! +//! âš ï¸ Duniter-v2s is under active development. +//! +//! 🚧 A test network called "ÄžDev" is deployed, allowing testing of wallets and indexers. +//! +//! ## Crate Overview +//! +//! This workspace consists of multiple crates that collaboratively implement the Duniter node, enabling features such as identity management, Web of Trust (WoT) evaluation, universal dividend calculation, and more. Below is a categorized list of crates within this workspace: +//! +//! ### Core Components +//! - [`client/distance`](../dc_distance/index.html): Provides an inherent data provider for distance evaluation in the Web of Trust. +//! - [`distance-oracle`](../distance_oracle/index.html): A standalone tool for performing computationally intensive Web of Trust distance calculations. +//! - [`node`](../duniter/index.html): The main node implementation for running the Duniter blockchain network. +//! +//! ### Testing Utilities +//! - [`end2end-tests`](../duniter_end2end_tests/index.html): End-to-end tests for validating the entire Duniter workflow. +//! - [`live-tests`](../duniter_live_tests/index.html): Live test cases for ensuring the integrity of the chain. +//! +//! ### Pallets (Runtime Modules) +//! - [`pallets/authority-members`](../pallet_authority_members/index.html): Manages the authority members. +//! - [`pallets/certification`](../pallet_certification/index.html): Handles identity certification. +//! - [`pallets/distance`](../pallet_distance/index.html): Implements the storage and logic for WoT distance calculations. +//! - [`pallets/duniter-test-parameters`](../pallet_duniter_test_parameters/index.html): Provides runtime testing parameters. +//! - [`pallets/duniter-test-parameters/macro`](../pallet_duniter_test_parameters_macro/index.html): Macros to simplify testing configurations. +//! - [`pallets/duniter-wot`](../pallet_duniter_wot/index.html): Core logic for managing the WoT. +//! - [`pallets/identity`](../pallet_identity/index.html): Implements identity management. +//! - [`pallets/membership`](../pallet_membership/index.html): Manages memberships. +//! - [`pallets/oneshot-account`](../pallet_oneshot_account/index.html): Manages one-shot accounts. +//! - [`pallets/quota`](../pallet_quota/index.html): Manages users quotas. +//! - [`pallets/smith-members`](../pallet_smith_members/index.html): Manages smiths. +//! - [`pallets/universal-dividend`](../pallet_universal_dividend/index.html): Handles the logic for distributing universal dividends. +//! - [`pallets/upgrade-origin`](../pallet_upgrade_origin/index.html): Ensures secure origins for runtime upgrades. +//! +//! ### Shared Primitives +//! - [`primitives/distance`](../sp_distance/index.html): Shared types and logic for distance evaluations. +//! - [`primitives/membership`](../sp_membership/index.html): Shared primitives for membership-related operations. +//! +//! ### Tooling and Utilities +//! - [`resources/weight_analyzer`](../weightanalyzer/index.html): Provides tools for analyzing runtime weights. +//! - [`runtime/common`](../common_runtime/index.html): Shared components and utilities used across multiple runtimes. +//! - [`runtime/gdev`](../gdev_runtime/index.html): The runtime implementation for the GDEV test network. +//! - [`runtime/g1`](../g1_runtime/index.html): The runtime implementation for the G1 test network. +//! - [`runtime/gtest`](../gtest_runtime/index.html): The runtime implementation for the GTEST test network. +//! - [`xtask`](../xtask/index.html): A custom xtask runner to automate release and testing. + pub mod chain_spec; pub mod cli; pub mod command; diff --git a/pallets/README.md b/pallets/README.md index e25c7907f..1c7abcfbb 100644 --- a/pallets/README.md +++ b/pallets/README.md @@ -6,20 +6,20 @@ Duniter uses some [parity pallets](https://github.com/duniter/substrate/tree/mas These pallets are at the core of Duniter/Äž1 currency -- **`authority-members`** Duniter authorities are not selected with staking but through a smith web of trust. -- **`certification`** Certifications are the "edges" of Duniter's dynamic directed graph. They mean the acceptation of a Licence. -- **`duniter-account`** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `linked_idty`. -- **`duniter-wot`** Merges identities, membership, certifications and distance pallets to implement Duniter Web of Trust. -- **`distance`** Publishes median of distance computation results provided by inherents coming from `distance-oracle` workers. -- **`identity`** Identities are the "nodes" of Duniter's dynamic directed graph. They are one-to-one mapping to human being. -- **`membership`** Membership defines the state of identities. They can be member or not of the different WoTs. -- **`universal-dividend`** UD is at the basis of Äž1 "libre currency". It is both a kind of "basic income" and a measure unit. +- **[`authority-members`](https://doc-duniter-org.ipns.pagu.re/pallet_authority_members/index.html)** Duniter authorities are not selected with staking but through a smith web of trust. +- **[`certification`](https://doc-duniter-org.ipns.pagu.re/pallet_certification/index.html)** Certifications are the "edges" of Duniter's dynamic directed graph. They mean the acceptation of a Licence. +- **[`duniter-account`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_account/index.html)** Duniter customized the `AccountData` defined in the `Balances` pallet to introduce a `linked_idty`. +- **[`duniter-wot`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_wot/index.html)** Merges identities, membership, certifications and distance pallets to implement Duniter Web of Trust. +- **[`distance`](https://doc-duniter-org.ipns.pagu.re/pallet_distance/index.html)** Publishes median of distance computation results provided by inherents coming from `distance-oracle` workers. +- **[`identity`](https://doc-duniter-org.ipns.pagu.re/pallet_identity/index.html)** Identities are the "nodes" of Duniter's dynamic directed graph. They are one-to-one mapping to human being. +- **[`membership`](https://doc-duniter-org.ipns.pagu.re/pallet_membership/index.html)** Membership defines the state of identities. They can be member or not of the different WoTs. +- **[`universal-dividend`](https://doc-duniter-org.ipns.pagu.re/pallet_universal_dividend/index.html)** UD is at the basis of Äž1 "libre currency". It is both a kind of "basic income" and a measure unit. ## Functional pallets -- **`duniter-test-parameters`** Test parameters only used in ÄžDev to allow tweaking parameters more easily. -- **`offences`** Sorts offences that will be executed by the `authority-members` pallet. -- **`oneshot-account`** Oneshot accounts are light accounts only used once for anonimity or convenience use case. -- **`provide-randomness`** Lets blockchain users ask for a verifiable random number. -- **`session-benchmarking`** Benchmarks the session pallet. -- **`upgrade-origin`** Allows some origins to dispatch a call as root. \ No newline at end of file +- **[`duniter-test-parameters`](https://doc-duniter-org.ipns.pagu.re/pallet_duniter_test_parameters/index.html)** Test parameters only used in ÄžDev to allow tweaking parameters more easily. +- **[`offences`](https://doc-duniter-org.ipns.pagu.re/pallet_offences/index.html)** Sorts offences that will be executed by the `authority-members` pallet. +- **[`oneshot-account`](https://doc-duniter-org.ipns.pagu.re/pallet_oneshot_account/index.html)** Oneshot accounts are light accounts only used once for anonymity or convenience use case. +- **[`provide-randomness`](https://doc-duniter-org.ipns.pagu.re/pallet_provide_randomness/index.html)** Lets blockchain users ask for a verifiable random number. +- **[`session-benchmarking`](https://doc-duniter-org.ipns.pagu.re/pallet_session_benchmarking/index.html)** Benchmarks the session pallet. +- **[`upgrade-origin`](https://doc-duniter-org.ipns.pagu.re/pallet_upgrade_origin/index.html)** Allows some origins to dispatch a call as root. diff --git a/pallets/distance/src/benchmarking.rs b/pallets/distance/src/benchmarking.rs index 8b3c3e00b..e88e8ec92 100644 --- a/pallets/distance/src/benchmarking.rs +++ b/pallets/distance/src/benchmarking.rs @@ -186,7 +186,7 @@ mod benchmarks { #[benchmark] fn do_evaluation_success() -> Result<(), BenchmarkError> { // Benchmarking do_evaluation in case of a single success. - CurrentPoolIndex::<T>::put(0); + CurrentPeriodIndex::<T>::put(0); // More than membership renewal to avoid antispam frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into()); let idty = T::IdtyIndex::one(); @@ -203,7 +203,7 @@ mod benchmarks { .into(), ); - CurrentPoolIndex::<T>::put(2); + CurrentPeriodIndex::<T>::put(2); Pallet::<T>::force_update_evaluation( RawOrigin::Root.into(), caller, @@ -230,7 +230,7 @@ mod benchmarks { #[benchmark] fn do_evaluation_failure() -> Result<(), BenchmarkError> { // Benchmarking do_evaluation in case of a single failure. - CurrentPoolIndex::<T>::put(0); + CurrentPeriodIndex::<T>::put(0); // More than membership renewal to avoid antispam frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into()); let idty = T::IdtyIndex::one(); @@ -247,7 +247,7 @@ mod benchmarks { .into(), ); - CurrentPoolIndex::<T>::put(2); + CurrentPeriodIndex::<T>::put(2); Pallet::<T>::force_update_evaluation( RawOrigin::Root.into(), caller, diff --git a/pallets/distance/src/lib.rs b/pallets/distance/src/lib.rs index f1866bf17..738ee5909 100644 --- a/pallets/distance/src/lib.rs +++ b/pallets/distance/src/lib.rs @@ -234,10 +234,10 @@ pub mod pallet { #[pallet::storage] pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>; - /// The current evaluation pool index. + /// The current evaluation period index. #[pallet::storage] - #[pallet::getter(fn current_pool_index)] - pub(super) type CurrentPoolIndex<T: Config> = StorageValue<_, u32, ValueQuery>; + #[pallet::getter(fn current_period_index)] + pub(super) type CurrentPeriodIndex<T: Config> = StorageValue<_, u32, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -300,7 +300,7 @@ pub mod pallet { #[pallet::genesis_build] impl<T: Config> BuildGenesisConfig for GenesisConfig<T> { fn build(&self) { - CurrentPoolIndex::<T>::put(0u32); + CurrentPeriodIndex::<T>::put(0u32); } } @@ -314,10 +314,10 @@ pub mod pallet { if block % BlockNumberFor::<T>::one().saturating_mul(T::EvaluationPeriod::get().into()) == BlockNumberFor::<T>::zero() { - let index = (CurrentPoolIndex::<T>::get() + 1) % 3; - CurrentPoolIndex::<T>::put(index); + let index = CurrentPeriodIndex::<T>::get() + 1; + CurrentPeriodIndex::<T>::put(index); weight = weight - .saturating_add(Self::do_evaluation(index)) + .saturating_add(Self::do_evaluation(index % 3)) .saturating_add(T::DbWeight::get().reads_writes(1, 1)); } weight.saturating_add(<T as pallet::Config>::WeightInfo::on_finalize()) @@ -557,7 +557,7 @@ pub mod pallet { who: &T::AccountId, idty_index: <T as pallet_identity::Config>::IdtyIndex, ) -> Result<(), DispatchError> { - Pallet::<T>::mutate_current_pool(CurrentPoolIndex::<T>::get(), |current_pool| { + Pallet::<T>::mutate_current_pool(CurrentPeriodIndex::<T>::get() % 3, |current_pool| { // extrinsics are transactional by default, this check might not be needed ensure!( current_pool.evaluations.len() < (MAX_EVALUATIONS_PER_SESSION as usize), @@ -590,7 +590,7 @@ pub mod pallet { evaluator: <T as frame_system::Config>::AccountId, computation_result: ComputationResult, ) -> DispatchResult { - Pallet::<T>::mutate_next_pool(CurrentPoolIndex::<T>::get(), |result_pool| { + Pallet::<T>::mutate_next_pool(CurrentPeriodIndex::<T>::get() % 3, |result_pool| { // evaluation must be provided for all identities (no more, no less) ensure!( computation_result.distances.len() == result_pool.evaluations.len(), diff --git a/resources/metadata.scale b/resources/metadata.scale index 74a7f95ffee5af127794fdb6bb9346fc4dee3cbe..db221c7f4528732b3c91dd29a07b5d5d748e2599 100644 GIT binary patch delta 66 zcmeB~$T?vmXG06)7A7kpO%LbNqN3Eil7Q5r%={G3yp+@mMg{=^1|VRWq7agis*ns( QF#Wy|lj!ymAtnhk0LUN|Pyhe` delta 54 zcmbO*k+WwaXG06)7A7kpSqJCRqN3Eil7RgD9M8Oz)Cxuh0RaXeVChi^nZB!$QEGds I5R<eS0NI!i%>V!Z diff --git a/runtime/README.md b/runtime/README.md index 4f74e8bea..88b4bdda5 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -2,7 +2,7 @@ Duniter client can run several runtimes. -- ÄžDev is for development purpose -- ÄžTest is to prepare Äž1 migration and test features before deploying on Ǧ1 -- Äž1 is the production currency +- [ÄžDev](https://doc-duniter-org.ipns.pagu.re/gdev_runtime/index.html) is for development purpose +- [ÄžTest](https://doc-duniter-org.ipns.pagu.re/gtest_runtime/index.html) is to prepare Äž1 migration and test features before deploying on Ǧ1 +- [Äž1](https://doc-duniter-org.ipns.pagu.re/g1_runtime/index.html) is the production currency diff --git a/xtask/src/gen_doc.rs b/xtask/src/gen_doc.rs index 9e4adf51b..91798167d 100644 --- a/xtask/src/gen_doc.rs +++ b/xtask/src/gen_doc.rs @@ -313,6 +313,7 @@ pub(super) fn gen_doc() -> Result<()> { Command::new("cargo") .args([ "doc", + "--package=duniter", "--package=pallet-*", "--package=*-runtime", "--package=*distance*", -- GitLab