diff --git a/client/distance/README.md b/client/distance/README.md new file mode 100644 index 0000000000000000000000000000000000000000..dff4c144c9bb3622cd38dd213165e8b1dfdbde19 --- /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 19c3fe661bba38bcefde6296da2d0fca01597d91..c36091612c42bb77aca3b7a61ef62e796f4ad713 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 60869ed6f298ed4f9df73e9ed08a8d6e1c90d3d0..2be78ee9c24c65a80dd54cf8668d6a04f2d56e67 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 cff2d054623851b24ed9004da5209b51ae1c1441..169549017b4343608b378e72f4078cbe6e51e5d0 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 16f98a61bfc7f824429d84b06e316e66ebf274ee..9e507dd031907d919a5340c5c3d2a9d0e4db57c0 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 89a4fa966c03a3b6336d87132136e052b04c4249..a9808972784453a8f7bbab770eeffa2552969483 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 aaa580fe31a7b08f9d6095fb8c210c4abc742be1..d9158414156b1064c6c610fd1e703ba894b73bf1 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 f5de146c6a91f0d024b917738176645006231d54..881022205f92e5725ad8be079a6f84d8fd2093b3 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 a1c197fa82121eb99b9e16293e85368a916404d4..23df3f2874b5f46ce9b33abb7e1152f08e338058 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 f9f43d6707559ea0145d72ff043b0b7ecd50cc09..f7d02df5544cf80a7b8922d90296d2ae8478ad85 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 0000000000000000000000000000000000000000..a365610b2da3efdf3b022dcead65843b57d6bcd5 --- /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 196e29d0fcefe7df37d76a47f06216327948e2f6..4a29ae0c07ff17c392e48fafe8690ac4ee2e81cb 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 9aa124d912fb453143f61420438c5ac90ded6bf5..58eabb0144c3414f29873103599e48f952d79252 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 caf6e6f327cfa0eda6ce001d9a153aae8f1f4401..2e557430013a21a5918a9ea56e3f4decfc38d470 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 e25c7907f787bcd0919f56e8379242874624bd07..1c7abcfbbaa5390bd0aa2e25376bb87df9b15346 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 8b3c3e00b509470c67bbc9e8c3a1a09a1ba28ecb..e88e8ec92068a3bfb8420ee06335b5d1fc0e5f8a 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 f1866bf17e584a6e5f52a68be25825beb2d0a650..738ee59099fe5be6207f839093888f244d316760 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 Binary files a/resources/metadata.scale and b/resources/metadata.scale differ diff --git a/runtime/README.md b/runtime/README.md index 4f74e8beafea64b015299da196c4d23e0941dc20..88b4bdda55ccd9c30b96a8f1fe12853a08621106 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 9e4adf51bf7166cdfa8081a4f68281edc2002479..91798167d7e54bad9fc8ff6c87548075977f063b 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*",