Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • update-docker-compose-rpc-squid-names
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
80 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-rework-certs
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-cucumber-identity
  • master
  • no-bootnodes
  • poc-oneshot-accounts
  • release/runtime-100
  • release/runtime-200
  • ts-types
  • ud-time-64
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • v0.1.0
28 results
Show changes
Showing
with 1470 additions and 183 deletions
[package]
edition.workspace = true
homepage.workspace = true
license.workspace = true
description = "Duniter client distance"
name = "dc-distance"
readme = "README.md"
version = "1.0.0"
repository.workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[features]
std = [
"frame-support/std",
"pallet-distance/std",
"sp-core/std",
"sp-distance/std",
"sp-runtime/std",
]
runtime-benchmarks = [
"frame-support/runtime-benchmarks",
"pallet-distance/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"pallet-distance/try-runtime",
"sp-distance/try-runtime",
"sp-runtime/try-runtime",
]
[dependencies]
frame-support = { workspace = true }
log = { workspace = true }
pallet-distance = { workspace = true }
sc-client-api = { workspace = true }
sp-core = { workspace = true }
sp-distance = { workspace = true }
sp-runtime = { workspace = true }
thiserror = { workspace = true }
# 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).
// 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/>.
//! # 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)]
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>(
client: &C,
parent: B::Hash,
distance_dir: PathBuf,
owner_keys: &[sp_core::sr25519::Public],
) -> sp_distance::InherentDataProvider<IdtyIndex>
where
B: BlockT,
C: ProvideUncles<B> + StorageProvider<B, Backend>,
Backend: sc_client_api::Backend<B>,
IdtyIndex: Decode + Encode + PartialEq + TypeInfo,
{
// Retrieve the period_index from storage.
let period_index = client
.storage(
parent,
&StorageKey(
frame_support::storage::storage_prefix(b"Distance", b"CurrentPeriodIndex").to_vec(),
),
)
.ok()
.flatten()
.and_then(|raw| u32::decode(&mut &raw.0[..]).ok());
// Return early if the storage is inaccessible or the data is corrupted.
let period_index = match period_index {
Some(index) => index,
None => {
log::error!("🧙 [distance inherent] PeriodIndex decoding failed.");
return sp_distance::InherentDataProvider::<IdtyIndex>::new(None);
}
};
// Retrieve the published_results from storage.
let published_results = client
.storage(
parent,
&StorageKey(
frame_support::storage::storage_prefix(
b"Distance",
match period_index % 3 {
0 => b"EvaluationPool0",
1 => b"EvaluationPool1",
2 => b"EvaluationPool2",
_ => unreachable!("n<3"),
},
)
.to_vec(),
),
)
.ok()
.flatten()
.and_then(|raw| {
pallet_distance::EvaluationPool::<AccountId32, IdtyIndex>::decode(&mut &raw.0[..]).ok()
});
// Return early if the storage is inaccessible or the data is corrupted.
let published_results = match published_results {
Some(published_results) => published_results,
None => {
log::info!("🧙 [distance inherent] No published result at this block.");
return sp_distance::InherentDataProvider::<IdtyIndex>::new(None);
}
};
// Find the account associated with the BABE key that is in our owner keys.
let mut local_account = None;
for key in owner_keys {
// Session::KeyOwner is StorageMap<_, Twox64Concat, (KeyTypeId, Vec<u8>), AccountId32, OptionQuery>
// Slices (variable length) and array (fixed length) are encoded differently, so the `.as_slice()` is needed
let item_key = (sp_runtime::KeyTypeId(*b"babe"), key.0.as_slice()).encode();
let mut storage_key =
frame_support::storage::storage_prefix(b"Session", b"KeyOwner").to_vec();
storage_key.extend_from_slice(&sp_core::twox_64(&item_key));
storage_key.extend_from_slice(&item_key);
if let Some(raw_data) = client
.storage(parent, &StorageKey(storage_key))
.ok()
.flatten()
{
if let Ok(key_owner) = AccountId32::decode(&mut &raw_data.0[..]) {
local_account = Some(key_owner);
break;
} else {
log::warn!("🧙 [distance inherent] Cannot decode key owner value");
}
}
}
// Have we already published a result for this period?
if let Some(local_account) = local_account {
if published_results.evaluators.contains(&local_account) {
log::debug!("🧙 [distance inherent] Already published a result for this period");
return sp_distance::InherentDataProvider::<IdtyIndex>::new(None);
}
} else {
log::error!("🧙 [distance inherent] Cannot find our BABE owner key");
return sp_distance::InherentDataProvider::<IdtyIndex>::new(None);
}
// Read evaluation result from file, if it exists
log::debug!(
"🧙 [distance inherent] Reading evaluation result from file {:?}",
distance_dir.clone().join(period_index.to_string())
);
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 inherent] Evaluation result file not found. Please ensure that the oracle version matches {}", VERSION_PREFIX);
}
_ => {
log::error!(
"🧙 [distance inherent] Cannot read distance evaluation result file: {e:?}"
);
}
}
return sp_distance::InherentDataProvider::<IdtyIndex>::new(None);
}
};
log::info!("🧙 [distance inherent] Providing evaluation result");
sp_distance::InherentDataProvider::<IdtyIndex>::new(Some(
sp_distance::ComputationResult::decode(&mut evaluation_result.as_slice()).unwrap(),
))
}
[package]
name = "distance-oracle"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[[bin]]
name = "distance-oracle"
required-features = ["standalone"]
[features]
default = ["standalone", "std"]
# Feature standalone is for CLI executable
standalone = ["clap", "tokio"]
# Feature std is needed
std = [
"codec/std",
"fnv/std",
"sp-core/std",
"sp-distance/std",
"sp-runtime/std",
]
try-runtime = ["sp-distance/try-runtime", "sp-runtime/try-runtime"]
[dependencies]
clap = { workspace = true, features = ["derive"], optional = true }
codec = { workspace = true }
fnv = { workspace = true }
log = { workspace = true }
rayon = { workspace = true }
simple_logger = { workspace = true }
sp-core = { workspace = true }
sp-distance = { workspace = true }
sp-runtime = { workspace = true }
subxt = { workspace = true, features = [
"native",
"jsonrpsee",
] }
tokio = { workspace = true, features = [
"rt-multi-thread",
"macros",
], optional = true }
[dev-dependencies]
bincode = { workspace = true }
dubp-wot = { workspace = true }
flate2 = { workspace = true, features = ["zlib-ng-compat"] }
# Distance Oracle
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).
// Copyright 2023 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/>.
#![allow(clippy::type_complexity)]
use crate::runtime;
use log::debug;
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)
.await
.expect("Cannot create RPC client")
}
pub async fn parent_hash(client: &Client) -> H256 {
client
.blocks()
.at_latest()
.await
.expect("Cannot fetch latest block hash")
.hash()
}
pub async fn current_period_index(client: &Client, parent_hash: H256) -> u32 {
client
.storage()
.at(parent_hash)
.fetch(&runtime::storage().distance().current_period_index())
.await
.expect("Cannot fetch current pool index")
.unwrap_or_default()
}
pub async fn current_pool(
client: &Client,
parent_hash: H256,
current_pool_index: u32,
) -> Option<EvaluationPool> {
client
.storage()
.at(parent_hash)
.fetch(&match current_pool_index {
0 => {
debug!("Looking at Pool1 for pool index {}", current_pool_index);
runtime::storage().distance().evaluation_pool1()
}
1 => {
debug!("Looking at Pool2 for pool index {}", current_pool_index);
runtime::storage().distance().evaluation_pool2()
}
2 => {
debug!("Looking at Pool0 for pool index {}", current_pool_index);
runtime::storage().distance().evaluation_pool0()
}
_ => unreachable!("n<3"),
})
.await
.expect("Cannot fetch current pool")
}
pub async fn evaluation_block(client: &Client, parent_hash: H256) -> H256 {
client
.storage()
.at(parent_hash)
.fetch(&runtime::storage().distance().evaluation_block())
.await
.expect("Cannot fetch evaluation block")
.expect("No evaluation block")
}
pub async fn max_referee_distance(client: &Client) -> u32 {
client
.constants()
.at(&runtime::constants().distance().max_referee_distance())
.expect("Cannot fetch referee distance")
}
pub async fn member_iter(client: &Client, evaluation_block: H256) -> MemberIter {
MemberIter(
client
.storage()
.at(evaluation_block)
.iter(runtime::storage().membership().membership_iter())
.await
.expect("Cannot fetch memberships"),
)
}
pub struct MemberIter(
subxt::backend::StreamOfResults<
subxt::storage::StorageKeyValuePair<
subxt::storage::StaticAddress<
(),
runtime::runtime_types::sp_membership::MembershipData<u32>,
(),
(),
subxt::utils::Yes,
>,
>,
>,
);
impl MemberIter {
pub async fn next(&mut self) -> Result<Option<IdtyIndex>, subxt::error::Error> {
self.0
.next()
.await
.transpose()
.map(|i| i.map(|j| idty_id_from_storage_key(&j.key_bytes)))
}
}
pub async fn cert_iter(client: &Client, evaluation_block: H256) -> CertIter {
CertIter(
client
.storage()
.at(evaluation_block)
.iter(runtime::storage().certification().certs_by_receiver_iter())
.await
.expect("Cannot fetch certifications"),
)
}
pub struct CertIter(
subxt::backend::StreamOfResults<
subxt::storage::StorageKeyValuePair<
subxt::storage::StaticAddress<
(),
Vec<(u32, u32)>,
(),
subxt::utils::Yes,
subxt::utils::Yes,
>,
>,
>,
);
impl CertIter {
pub async fn next(
&mut self,
) -> Result<Option<(IdtyIndex, Vec<(IdtyIndex, u32)>)>, subxt::error::Error> {
self.0
.next()
.await
.transpose()
.map(|i| i.map(|j| (idty_id_from_storage_key(&j.key_bytes), j.value)))
}
}
fn idty_id_from_storage_key(storage_key: &[u8]) -> IdtyIndex {
u32::from_le_bytes(
storage_key[40..44]
.try_into()
.expect("Cannot convert StorageKey to IdtyIndex"),
)
}
// Copyright 2023 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/>.
//! # 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)]
pub mod mock;
#[cfg(test)]
mod tests;
#[cfg(test)]
pub use mock as api;
use api::{AccountId, EvaluationPool, IdtyIndex, H256};
use codec::Encode;
use fnv::{FnvHashMap, FnvHashSet};
use log::{debug, info, warn};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use std::{io::Write, path::PathBuf};
/// 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 {}
pub enum RuntimeConfig {}
impl subxt::config::Config for RuntimeConfig {
type AccountId = AccountId;
type Address = sp_runtime::MultiAddress<Self::AccountId, u32>;
type AssetId = ();
type ExtrinsicParams = subxt::config::substrate::SubstrateExtrinsicParams<Self>;
type Hash = subxt::utils::H256;
type Hasher = subxt::config::substrate::BlakeTwo256;
type Header =
subxt::config::substrate::SubstrateHeader<u32, subxt::config::substrate::BlakeTwo256>;
type Signature = sp_runtime::MultiSignature;
}
/// Represents a tipping amount.
#[derive(Copy, Clone, Debug, Default, Encode)]
pub struct Tip {
#[codec(compact)]
tip: u64,
}
impl Tip {
pub fn new(amount: u64) -> Self {
Tip { tip: amount }
}
}
impl From<u64> for Tip {
fn from(n: u64) -> Self {
Self::new(n)
}
}
/// Represents configuration parameters.
pub struct Settings {
pub evaluation_result_dir: PathBuf,
pub rpc_url: String,
}
impl Default for Settings {
fn default() -> Self {
Self {
evaluation_result_dir: PathBuf::from("/tmp/duniter/chains/gdev/distance"),
rpc_url: String::from("ws://127.0.0.1:9944"),
}
}
}
/// 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;
};
debug!("Saving distance evaluation result to file `{evaluation_result_path:?}`");
let mut evaluation_result_file = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(&evaluation_result_path)
.unwrap_or_else(|e| {
panic!(
"Cannot open distance evaluation result file `{evaluation_result_path:?}`: {e:?}"
)
});
evaluation_result_file
.write_all(
&sp_distance::ComputationResult {
distances: evaluation,
}
.encode(),
)
.unwrap_or_else(|e| {
panic!(
"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));
});
}
/// 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,
) -> Option<(Vec<sp_runtime::Perbill>, u32, PathBuf)> {
let (evaluation_block, current_period_index, evaluation_pool, evaluation_result_path) =
prepare_evaluation_context(client, settings).await?;
info!("Evaluating distance for period {}", current_period_index);
let max_depth = api::max_referee_distance(client).await;
// member idty -> issued certs
let mut members = FnvHashMap::<IdtyIndex, u32>::default();
let mut members_iter = api::member_iter(client, evaluation_block).await;
while let Some(member_idty) = members_iter
.next()
.await
.expect("Cannot fetch next members")
{
members.insert(member_idty, 0);
}
let min_certs_for_referee = (members.len() as f32).powf(1. / (max_depth as f32)).ceil() as u32;
// idty -> received certs
let mut received_certs = FnvHashMap::<IdtyIndex, Vec<IdtyIndex>>::default();
let mut certs_iter = api::cert_iter(client, evaluation_block).await;
while let Some((receiver, issuers)) = certs_iter
.next()
.await
.expect("Cannot fetch next certification")
{
if (issuers.len() as u32) < min_certs_for_referee {
// This member is not referee
members.remove(&receiver);
}
for (issuer, _removable_on) in issuers.iter() {
if let Some(issued_certs) = members.get_mut(issuer) {
*issued_certs += 1;
}
}
received_certs.insert(
receiver,
issuers
.into_iter()
.map(|(issuer, _removable_on)| issuer)
.collect(),
);
}
// Only retain referees
members.retain(|_idty, issued_certs| *issued_certs >= min_certs_for_referee);
let referees = members;
let evaluation = evaluation_pool
.evaluations
.0
.as_slice()
.par_iter()
.map(|(idty, _)| distance_rule(&received_certs, &referees, max_depth, *idty))
.collect();
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>,
idty: IdtyIndex,
accessible_referees: &mut FnvHashSet<IdtyIndex>,
known_idties: &mut FnvHashMap<IdtyIndex, u32>,
depth: u32,
) {
// Do not re-explore identities that have already been explored at least as deeply
match known_idties.entry(idty) {
std::collections::hash_map::Entry::Occupied(mut entry) => {
if *entry.get() >= depth {
return;
} else {
*entry.get_mut() = depth;
}
}
std::collections::hash_map::Entry::Vacant(entry) => {
entry.insert(depth);
}
}
// If referee, add it to the list
if referees.contains_key(&idty) {
accessible_referees.insert(idty);
}
// If reached the maximum distance, stop exploring
if depth == 0 {
return;
}
// Explore certifiers
for &certifier in received_certs.get(&idty).unwrap_or(&vec![]).iter() {
distance_rule_recursive(
received_certs,
referees,
certifier,
accessible_referees,
known_idties,
depth - 1,
);
}
}
/// 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>,
depth: u32,
idty: IdtyIndex,
) -> sp_runtime::Perbill {
debug!("Evaluating distance for idty {}", idty);
let mut accessible_referees =
FnvHashSet::<IdtyIndex>::with_capacity_and_hasher(referees.len(), Default::default());
let mut known_idties =
FnvHashMap::<IdtyIndex, u32>::with_capacity_and_hasher(referees.len(), Default::default());
distance_rule_recursive(
received_certs,
referees,
idty,
&mut accessible_referees,
&mut known_idties,
depth,
);
let result = if referees.contains_key(&idty) {
sp_runtime::Perbill::from_rational(
accessible_referees.len() as u32 - 1,
referees.len() as u32 - 1,
)
} else {
sp_runtime::Perbill::from_rational(accessible_referees.len() as u32, referees.len() as u32)
};
info!(
"Distance for idty {}: {}/{} = {}%",
idty,
accessible_referees.len(),
referees.len(),
result.deconstruct() as f32 / 1_000_000_000f32 * 100f32
);
result
}
// Copyright 2023-2024 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 clap::Parser;
#[derive(Debug, clap::Parser)]
struct Cli {
#[clap(short = 'd', long, default_value = "/tmp/duniter/chains/gdev/distance")]
evaluation_result_dir: String,
/// Number of seconds between two evaluations (oneshot if absent)
#[clap(short = 'i', long)]
interval: Option<u64>,
/// Node used for fetching state
#[clap(short = 'u', long, default_value = "ws://127.0.0.1:9944")]
rpc_url: String,
/// Log level (off, error, warn, info, debug, trace)
#[clap(short = 'l', long, default_value = "info")]
log: log::LevelFilter,
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
simple_logger::SimpleLogger::new()
.with_level(cli.log)
.init()
.unwrap();
let client = distance_oracle::api::client(&cli.rpc_url).await;
let settings = distance_oracle::Settings {
evaluation_result_dir: cli.evaluation_result_dir.into(),
rpc_url: cli.rpc_url,
};
if let Some(duration) = cli.interval {
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(&client, &settings).await;
interval.tick().await;
}
} else {
distance_oracle::run(&client, &settings).await;
}
}
// Copyright 2023 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::runtime::runtime_types::{
pallet_distance::median::MedianAcc, sp_arithmetic::per_things::Perbill,
};
use dubp_wot::{data::rusty::RustyWebOfTrust, WebOfTrust, WotId};
use std::collections::BTreeSet;
pub struct Client {
wot: RustyWebOfTrust,
pub pool_len: usize,
}
pub type AccountId = sp_runtime::AccountId32;
pub type IdtyIndex = u32;
pub type H256 = subxt::utils::H256;
pub struct EvaluationPool {
pub evaluations: (Vec<(IdtyIndex, MedianAcc<Perbill>)>,),
pub evaluators: BTreeSet<AccountId>,
}
pub async fn client(_rpc_url: impl AsRef<str>) -> Client {
unimplemented!()
}
pub fn client_from_wot(wot: RustyWebOfTrust) -> Client {
Client { wot, pool_len: 1 }
}
pub async fn parent_hash(_client: &Client) -> H256 {
Default::default()
}
pub async fn current_period_index(_client: &Client, _parent_hash: H256) -> u32 {
0
}
pub async fn current_pool(
client: &Client,
_parent_hash: H256,
_current_session: u32,
) -> Option<EvaluationPool> {
Some(EvaluationPool {
evaluations: (client
.wot
.get_enabled()
.into_iter()
.chain(client.wot.get_disabled().into_iter())
.zip(0..client.pool_len)
.map(|(wot_id, _)| {
(wot_id.0 as IdtyIndex, unsafe {
std::mem::transmute::<
(std::vec::Vec<()>, std::option::Option<u32>, i32),
MedianAcc<Perbill>,
>((Vec::<()>::new(), Option::<u32>::None, 0))
})
})
.collect(),),
evaluators: BTreeSet::new(),
})
}
pub async fn evaluation_block(_client: &Client, _parent_hash: H256) -> H256 {
Default::default()
}
pub async fn max_referee_distance(_client: &Client) -> u32 {
5
}
pub async fn member_iter(client: &Client, _evaluation_block: H256) -> MemberIter {
MemberIter(client.wot.get_enabled().into_iter())
}
pub struct MemberIter(std::vec::IntoIter<WotId>);
impl MemberIter {
pub async fn next(&mut self) -> Result<Option<IdtyIndex>, subxt::error::Error> {
Ok(self.0.next().map(|wot_id| wot_id.0 as u32))
}
}
pub async fn cert_iter(client: &Client, _evaluation_block: H256) -> CertIter {
CertIter(
client
.wot
.get_enabled()
.iter()
.chain(client.wot.get_disabled().iter())
.map(|wot_id| {
(
wot_id.0 as IdtyIndex,
client
.wot
.get_links_source(*wot_id)
.unwrap_or_default()
.into_iter()
.map(|wot_id| (wot_id.0 as IdtyIndex, 0))
.collect::<Vec<(IdtyIndex, u32)>>(),
)
})
.collect::<Vec<_>>()
.into_iter(),
)
}
pub struct CertIter(std::vec::IntoIter<(IdtyIndex, Vec<(IdtyIndex, u32)>)>);
impl CertIter {
pub async fn next(&mut self) -> Result<Option<(u32, Vec<(u32, u32)>)>, subxt::error::Error> {
Ok(self.0.next())
}
}
// Copyright 2023 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 dubp_wot::{
data::rusty::RustyWebOfTrust, operations::distance::DistanceCalculator, WebOfTrust,
};
use flate2::read::ZlibDecoder;
use sp_runtime::Perbill;
use std::{fs::File, io::Read};
#[tokio::test]
#[ignore = "long to execute"]
async fn test_distance_against_v1() {
let wot = wot_from_v1_file();
let n = wot.size();
let min_certs_for_referee = (wot.get_enabled().len() as f32).powf(1. / 5.).ceil() as u32;
// Reference implementation
let ref_calculator = dubp_wot::operations::distance::RustyDistanceCalculator;
let t_a = std::time::Instant::now();
let ref_results: Vec<Perbill> = wot
.get_enabled()
.into_iter()
.chain(wot.get_disabled().into_iter())
.zip(0..n)
.map(|(i, _)| {
let result = ref_calculator
.compute_distance(
&wot,
dubp_wot::operations::distance::WotDistanceParameters {
node: i,
sentry_requirement: min_certs_for_referee,
step_max: 5,
x_percent: 0.8,
},
)
.unwrap();
Perbill::from_rational(result.success, result.sentries)
})
.collect();
println!("ref time: {}", t_a.elapsed().as_millis());
// Our implementation
let mut client = crate::api::client_from_wot(wot);
client.pool_len = n;
let t_a = std::time::Instant::now();
let results = crate::compute_distance_evaluation(&client, &Default::default())
.await
.unwrap();
println!("new time: {}", t_a.elapsed().as_millis());
assert_eq!(results.0.len(), n);
let mut errors: Vec<_> = results
.0
.iter()
.zip(ref_results.iter())
.map(|(r, r_ref)| r.deconstruct() as i64 - r_ref.deconstruct() as i64)
.collect();
errors.sort_unstable();
println!(
"Error: {:?} / {:?} / {:?} / {:?} / {:?} (min / 1Q / med / 3Q / max)",
errors[0],
errors[errors.len() / 4],
errors[errors.len() / 2],
errors[errors.len() * 3 / 4],
errors[errors.len() - 1]
);
let correct_results = results
.0
.iter()
.zip(ref_results.iter())
.map(|(r, r_ref)| (r == r_ref) as usize)
.sum::<usize>();
println!("Correct results: {correct_results} / {n}");
assert_eq!(correct_results, n);
}
fn wot_from_v1_file() -> RustyWebOfTrust {
let file = File::open("wot.deflate").expect("Cannot open wot.deflate");
let mut decompressor = ZlibDecoder::new(file);
let mut decompressed_bytes = Vec::new();
decompressor
.read_to_end(&mut decompressed_bytes)
.expect("Cannot decompress wot.deflate");
bincode::deserialize::<RustyWebOfTrust>(&decompressed_bytes).expect("Cannot decode wot.deflate")
}
File added
version: "3.5"
# This is a minimal docker-compose.yml template for running a Duniter mirror node
# For more detailed examples, look at docker/compose folder
services:
duniter-v2s:
container_name: duniter-v2s
image: duniter/duniter-v2s:v0.1.0
duniter-v2s-mirror:
container_name: duniter-v2s-mirror
# the image tells which network you are connecting to
# here it is gdev network
image: duniter/duniter-v2s-gdev-800:latest
ports:
# telemetry
# prometheus telemetry to monitor resource use
- 9615:9615
# rpc
- 9933:9933
# rpc-ws
# RPC API (ws and http)
- 9944:9944
# p2p
# public p2p endpoint
- 30333:30333
environment:
DUNITER_INSTANCE_NAME: "duniter_local"
DUNITER_CHAIN_NAME: "dev"
#DUNITER_DISABLE_PROMETHEUS: "false"
# read https://duniter.org/wiki/duniter-v2/configure-docker/
# to configure these
DUNITER_NODE_NAME: duniter_local
DUNITER_CHAIN_NAME: gdev
DUNITER_PUBLIC_ADDR: /dns/your.domain.name/tcp/30333
DUNITER_LISTEN_ADDR: /ip4/0.0.0.0/tcp/30333
volumes:
- duniter-local-data:/var/lib/duniter
......
# Workaround for https://github.com/containers/buildah/issues/4742
FROM debian:bullseye-slim AS target
# ------------------------------------------------------------------------------
# Build Stage
# ------------------------------------------------------------------------------
# Building for Debian buster because we need the binary to be compatible
# with the image paritytech/ci-linux:production (currently based on
# debian:buster-slim) used by the gitlab CI
FROM rust:1-buster as build
# When building for a foreign arch, use cross-compilation
# https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
FROM --platform=$BUILDPLATFORM rust:1-bullseye AS build
ARG BUILDPLATFORM
ARG TARGETPLATFORM
# Debug
RUN echo "BUILDPLATFORM = $BUILDPLATFORM"
RUN echo "TARGETPLATFORM = $TARGETPLATFORM"
# We need the target arch triplet in both Debian and rust flavor
RUN echo "DEBIAN_ARCH_TRIPLET='$(dpkg-architecture -A${TARGETPLATFORM#linux/} -qDEB_TARGET_MULTIARCH)'" >>/root/dynenv
RUN . /root/dynenv && \
echo "RUST_ARCH_TRIPLET='$(echo "$DEBIAN_ARCH_TRIPLET" | sed -E 's/-linux-/-unknown&/')'" >>/root/dynenv
RUN cat /root/dynenv
WORKDIR /root
# Copy source tree
COPY . .
RUN test -x build/duniter || \
( \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y clang \
)
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get install -y clang cmake protobuf-compiler
# build duniter
ARG threads=1
RUN test -x build/duniter || \
( \
CARGO_PROFILE_RELEASE_LTO="true" \
cargo build --release -j $threads && \
mkdir -p build && \
mv target/release/duniter build/ \
)
ARG debug=0
RUN if [ "$debug" = 0 ]; then \
echo "CARGO_OPTIONS=--release" >>/root/dynenv && \
echo "TARGET_FOLDER=release" >>/root/dynenv; \
else \
echo "TARGET_FOLDER=debug" >>/root/dynenv; \
fi
# Create fake duniter-cucumber if is not exist
# The goal is to avoid error later, this binary is optional
RUN test -x build/duniter-cucumber || \
( \
# Configure cross-build environment if need be
RUN set -x && \
if [ "$TARGETPLATFORM" != "$BUILDPLATFORM" ]; then \
. /root/dynenv && \
apt install -y gcc-$DEBIAN_ARCH_TRIPLET binutils-$DEBIAN_ARCH_TRIPLET && \
rustup target add "$RUST_ARCH_TRIPLET" && \
: https://github.com/rust-lang/cargo/issues/4133 && \
echo "RUSTFLAGS='-C linker=$DEBIAN_ARCH_TRIPLET-gcc'; export RUSTFLAGS" >>/root/dynenv; \
fi
# Build
ARG chain="gdev"
RUN set -x && \
cat /root/dynenv && \
. /root/dynenv && \
cargo build -Zgit=shallow-deps --locked $CARGO_OPTIONS --no-default-features $BENCH_OPTIONS --features $chain --target "$RUST_ARCH_TRIPLET" && \
cargo build -Zgit=shallow-deps --locked $CARGO_OPTIONS --target "$RUST_ARCH_TRIPLET" --package distance-oracle && \
mkdir -p build && \
touch build/duniter-cucumber \
)
mv target/$RUST_ARCH_TRIPLET/$TARGET_FOLDER/duniter build/ && \
mv target/$RUST_ARCH_TRIPLET/$TARGET_FOLDER/distance-oracle build/
# Run tests if requested, except when cross-building
ARG cucumber=0
RUN if [ "$cucumber" != 0 ] && [ "$TARGETPLATFORM" = "$BUILDPLATFORM" ]; then \
cargo ta && \
cargo test -Zgit=shallow-deps --workspace --exclude duniter-end2end-tests --exclude duniter-live-tests --features=runtime-benchmarks,constant-fees \
cd target/debug/deps/ && \
rm cucumber_tests-*.d && \
mv cucumber_tests* ../../../build/duniter-cucumber; \
fi
# ------------------------------------------------------------------------------
# Final Stage
# ------------------------------------------------------------------------------
FROM debian:buster-slim
FROM target
LABEL maintainer="Gilles Filippini <gilles.filippini@pini.fr>"
LABEL version="0.0.0"
LABEL description="Crypto-currency software (based on Substrate framework) to operate Ğ1 libre currency"
# Required certificates for RPC connections
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates
RUN update-ca-certificates
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
RUN adduser --home /var/lib/duniter duniter
# Configuration
# rpc, rpc-ws, p2p, telemetry
EXPOSE 9933 9944 30333 9615
# rpc, p2p, telemetry
EXPOSE 9944 30333 9615
VOLUME /var/lib/duniter
ENTRYPOINT ["docker-entrypoint"]
USER duniter
# Intall
COPY --from=build /root/build/duniter /usr/local/bin/duniter
COPY --from=build /root/build/duniter-cucumber /usr/local/bin/duniter-cucumber
COPY --from=build /root/build /usr/local/bin/
COPY --from=build /root/dynenv /var/lib/duniter
COPY docker/docker-entrypoint /usr/local/bin/
COPY docker/docker-distance-entrypoint /usr/local/bin/
# Debug
RUN cat /var/lib/duniter/dynenv
# duniter/duniter-v2s
Duniter is the software that supports the [Ğ1 libre-currency blockchain](https://duniter.org/).
[Duniter v2s](https://git.duniter.org/nodes/rust/duniter-v2s) is a complete rewrite of Duniter based on the Substrate / Polkadot framework. **This is alpha state work in progress.**
## Minimal docker-compose file for an mirror node
```
version: "3.5"
services:
duniter-mirror:
image: duniter/duniter-v2s-gdev:latest
restart: unless-stopped
ports:
# Prometheus endpoint
- 9615:9615
# rpc
- 9944:9944
# p2p
- 30333:30333
volumes:
- data-mirror:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=gdev
- DUNITER_NODE_NAME=<my-node-name>
volumes:
data-mirror:
```
## Minimal docker-compose file for a validator node
```
version: "3.5"
services:
duniter-validator:
image: duniter/duniter-v2s-gdev:latest
restart: unless-stopped
ports:
# Prometheus endpoint
- 9615:9615
# p2p
- 30333:30333
volumes:
- data-validator:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=gdev
- DUNITER_VALIDATOR=true
- DUNITER_NODE_NAME=<my-validator-node-name>
volumes:
data-validator:
```
## Environment variables
| Name | Description | Default |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `DUNITER_NODE_NAME` | The node name. This name will appear on the Substrate telemetry server when telemetry is enabled. | Random name |
| `DUNITER_CHAIN_NAME` | The currency to process. "gdev" uses the embeded chainspec. A path allows to use a local json raw chainspec. | `dev` (development mode) |
| `DUNITER_PUBLIC_ADDR` | The libp2p public address base. See [libp2p documentation](https://docs.libp2p.io/concepts/fundamentals/addressing/). This variable is useful when the node is behind a reverse proxy with its ports not directly exposed.<br>Note: the `p2p/<peer_id>` part of the address shouldn't be set in this variable. It is automatically added by Duniter. | duniter-v2s guesses one from the node's IPv4 address. |
| `DUNITER_LISTEN_ADDR` | The libp2p listen address. See [libp2p documentation](https://docs.libp2p.io/concepts/fundamentals/addressing/). This variable is useful when running a validator node behind a reverse proxy, to force the P2P end point in websocket mode with:<br> `DUNITER_LISTEN_ADDR=/ip4/0.0.0.0/tcp/30333/ws` | Non validator node: `/ip4/0.0.0.0/tcp/30333/ws`<br>Validator node: `/ip4/0.0.0.0/tcp/30333` |
| `DUNITER_RPC_CORS` | Value of the polkadot `--rpc-cors` option. | `all` |
| `DUNITER_VALIDATOR` | Boolean (`true` / `false`) to run the node in validator mode. Configure the polkadot options `--validator --rpc-methods Unsafe`. | `false` |
| `DUNITER_DISABLE_PROMETHEUS` | Boolean to disable the Prometheus endpoint on port 9615. | `false` |
| `DUNITER_DISABLE_TELEMETRY` | Boolean to disable connecting to the Substrate telemetry server. | `false` |
| `DUNITER_PRUNING_PROFILE` | _ `default`<br> _ `archive`: keep all blocks and state blocks<br> \* `light`: keep only last 256 state blocks and last 14400 blocks (one day duration) | `default` |
| `DUNITER_PUBLIC_RPC` | The public RPC endpoint to gossip on the network and make available in the apps. | None |
| `DUNITER_PUBLIC_SQUID` | The public Squid graphql endpoint to gossip on the network and make available in the apps. | None |
| `DUNITER_PUBLIC_ENDPOINTS` | Path to a JSON file containing public endpoints to gossip on the network. The file should use the following format:<br>```{"endpoints": [ { "protocol": "rpc", "address": "wss://gdev.example.com" }, { "protocol": "squid", "address": "gdev.example.com/graphql/v1" }]}``` | None |
## Other Duniter options
You can pass any other option to Duniter using the `command` docker-compose element:
```
command:
# workaround for substrate issue #12073
# https://github.com/paritytech/substrate/issues/12073
- "--wasm-execution=interpreted-i-know-what-i-do"
```
## Start Duniter
Once you are happy with your `docker-compose.yml` file, run in the same folder:
```bash
docker compose up -d
```
## Running duniter subcommands or custom set of options
To run duniter from the command line without the default configuration detailed in the "Environment variables" section use `--` as the first argument. For example:
```
$ docker run --rm duniter/duniter-v2s-gdev:latest -- key generate
$ docker run --rm duniter/duniter-v2s-gdev:latest -- --chain gdev ...
```
FROM paritytech/ci-linux:production
# Set the working directory
WORKDIR /app/
# Copy the toolchain
COPY rust-toolchain.toml ./
# Install toolchain, substrate and cargo-deb with cargo cache
RUN --mount=type=cache,target=/root/.cargo \
cargo install cargo-deb
# Create a dummy project to cache dependencies
COPY Cargo.toml .
COPY rust-toolchain.toml ./
RUN --mount=type=cache,target=/app/target \
--mount=type=cache,target=/root/.cargo/registry \
mkdir src && \
sed -i '/git = \|version = /!d' Cargo.toml && \
sed -i 's/false/true/' Cargo.toml && \
sed -i '1s/^/\[package\]\nname\=\"Dummy\"\n\[dependencies\]\n/' Cargo.toml && \
echo "fn main() {}" > src/main.rs && \
cargo build -Zgit=shallow-deps --release && \
rm -rf src Cargo.lock Cargo.toml
# Copy the entire project
COPY . .
# Build the project and create Debian packages
RUN --mount=type=cache,target=/app/target \
--mount=type=cache,target=/root/.cargo/registry \
cargo build -Zgit=shallow-deps --release && \
cargo deb --no-build -p duniter && \
cp -r ./target/debian/ ./
# Clean up unnecessary files to reduce image size
RUN rm -rf /app/target/release /root/.cargo/registry
# docker-compose.yml template for running a Duniter smith node
# for more doc, see https://duniter.org/wiki/duniter-v2/
services:
# duniter smith node
duniter-v2s-smith:
container_name: duniter-v2s-smith
image: duniter/duniter-v2s-gdev-800:latest
ports:
# RPC API of a smith node should not be exposed publicly!
- 127.0.0.1:9944:9944
# public p2p endpoint
- 30333:30333
environment:
DUNITER_NODE_NAME: duniter_smith
DUNITER_CHAIN_NAME: gdev
DUNITER_VALIDATOR: true
DUNITER_PRUNING_PROFILE: light
DUNITER_PUBLIC_ADDR: /dns/your.domain.name/tcp/30333
DUNITER_LISTEN_ADDR: /ip4/0.0.0.0/tcp/30333
volumes:
- duniter-smith-data:/var/lib/duniter
# distance oracle
distance-oracle:
container_name: distance-oracle
# choose the version of the image here
image: duniter/duniter-v2s-gdev:latest
entrypoint: docker-distance-entrypoint
environment:
ORACLE_RPC_URL: ws://duniter-v2s-smith:9944
ORACLE_RESULT_DIR: /var/lib/duniter/chains/gdev/distance/
ORACLE_EXECUTION_INTERVAL: 1800
ORACLE_LOG_LEVEL: info
volumes:
- duniter-smith-data:/var/lib/duniter
volumes:
duniter-smith-data:
version: "3.4"
services:
duniter-rpc:
image: duniter/duniter-v2s:v0.1.0
restart: unless-stopped
ports:
# telemetry
- 127.0.0.1:9615:9615
# rpc
- 127.0.0.1:9933:9933
# rpc-ws
- 127.0.0.1:9944:9944
# p2p
- 30333:30333
volumes:
- ./node.key:/etc/duniter/node.key
- duniter-rpc-data:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=gdev
command:
- "--execution"
- "Wasm"
- "--node-key-file"
- "/var/lib/duniter/node.key"
- "--public-addr"
# SERVER_DOMAIN should be replaced by a domain name that point on your server
#
# The PEER_ID should be replaced by the output of this command:
# docker run --rm -it --entrypoint duniter -v $PWD:/var/lib/duniter/ duniter/duniter-v2s:v0.1.0 key generate-node-key --file /var/lib/duniter/node.key
- "/dns/SERVER_DOMAIN/tcp/30333/p2p/PEER_ID"
- "--rpc-cors"
- "all"
volumes:
duniter-rpc-data:
version: "3.4"
services:
duniter-rpc:
image: duniter/duniter-v2s:v0.1.0
restart: unless-stopped
ports:
# telemetry
- 127.0.0.1:9615:9615
# rpc
- 127.0.0.1:9933:9933
# rpc-ws
- 127.0.0.1:9944:9944
# p2p
- 30333:30333
volumes:
- ./duniter-rpc/:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=gdev
command:
- "--execution"
- "Wasm"
- "--node-key-file"
- "/var/lib/duniter/node-key"
- "--public-addr"
# SERVER_IP should be replaced by the public IP of your server
#
# The PEER_ID should be replaced by the output of this command:
# docker run --rm -it --entrypoint -v $PWD/duniter-rpc/:/var/lib/duniter/ duniter duniter/duniter-v2s:v0.1.0 key generate-node-key --file /var/lib/duniter/node-key.txt
- "/ip4/SERVER_IP/tcp/30333/p2p/PEER_ID"
- "--rpc-cors"
- "all"
duniter-validator:
image: duniter/duniter-v2s:v0.1.0
restart: unless-stopped
ports:
# telemetry
- 127.0.0.1:9616:9615
# rpc
- 127.0.0.1:9934:9933
# rpc-ws
- 127.0.0.1:9945:9944
# p2p
- 30334:30333
volumes:
- ./duniter-validator/:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=gdev
command:
- "--execution"
- "Wasm"
- "--node-key-file"
- "/var/lib/duniter/node-key"
- "--public-addr"
# SERVER_IP should be replaced by the public IP of your server
#
# The PEER_ID should be replaced by the output of this command:
# docker run --rm -it --entrypoint duniter -v $PWD/duniter-validator/:/var/lib/duniter/ duniter/duniter-v2s:v0.1.0 key generate-node-key --file /var/lib/duniter/node-key.txt
- "/ip4/SERVER_IP/tcp/30334/p2p/PEER_ID"
- "--rpc-methods=Unsafe"
- "--validator"
- "--rpc-cors"
- "all"
version: "3.4"
services:
duniter-rpc:
image: duniter/duniter-v2s:DUNITER_IMAGE_TAG
restart: unless-stopped
ports:
- "9944:9944"
- "30333:30333"
volumes:
- ./duniter-rpc/:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=/var/lib/duniter/CURRENCY-raw.json
command:
- "--bootnodes"
- "/dns/duniter-validator/tcp/30333/p2p/VALIDATOR_NODE_KEY"
- "--node-key-file"
- "/var/lib/duniter/node-key"
- "--rpc-cors"
- "all"
duniter-validator:
image: duniter/duniter-v2s:DUNITER_IMAGE_TAG
restart: unless-stopped
ports:
- "127.0.0.1:9945:9944"
- "30334:30333"
volumes:
- ./duniter-validator/:/var/lib/duniter/
environment:
- DUNITER_CHAIN_NAME=/var/lib/duniter/CURRENCY-raw.json
command:
- "--bootnodes"
- "/dns/duniter-rpc/tcp/30333/p2p/RPC_NODE_KEY"
- "--node-key-file"
- "/var/lib/duniter/node-key"
- "--rpc-methods=Unsafe"
- "--validator"
- "--rpc-cors"
- "all"
FROM rust:latest
RUN dpkg --add-architecture armhf && \
apt-get update && apt-get upgrade -y && \
apt-get install -y aptitude && \
aptitude install -y \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf \
pkg-config \
cmake \
libssl-dev \
git \
clang \
libclang-dev \
libssl-dev:armhf
# Install nightly with armv7 and w32-u-u
RUN rustup install nightly-2022-04-20-x86_64-unknown-linux-gnu && \
rustup target add armv7-unknown-linux-gnueabihf --toolchain \
nightly-2022-04-20-x86_64-unknown-linux-gnu && \
rustup target add wasm32-unknown-unknown --toolchain \
nightly-2022-04-20-x86_64-unknown-linux-gnu && \
cargo +nightly-2022-04-20-x86_64-unknown-linux-gnu install --git \
https://github.com/alexcrichton/wasm-gc --force
ENV CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER arm-linux-gnueabihf-gcc
ENV PKG_CONFIG_ALLOW_CROSS 1
ENV PKG_CONFIG_PATH /usr/lib/arm-linux-gnueabihf/pkgconfig/
# Disallow the `pkg-config` crate to look for the config for zlib, because build.rs of `libz-sys`
# gets confused and pulls the system-wide library (i.e. of the host) instead of the target when
# cross-compiling. This essentially leads to static linking of zlib.
#
# Alternatively, we can supply LIBZ_SYS_STATIC=1. Weirdly enough, installing libgtk-3-dev:armhf
# also solves the problem somehow.
#
# Here is the related issue: https://github.com/rust-lang/libz-sys/issues/49
ENV ZLIB_NO_PKG_CONFIG 1
# This is for compiling GUI apps.
# RUN aptitude install -y libasound2-dev:armhf libgtk-3-dev:armhf libsdl2-dev:armhf
RUN useradd rust --user-group --create-home --shell /bin/bash --groups sudo
WORKDIR /home/rust/src