From 6b7c97aeabb6f2074d79c5bb8ff0790d0dd0c98b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?C=C3=A9dric=20Moreau?= <cem.moreau@gmail.com>
Date: Fri, 5 Jan 2024 21:26:00 +0100
Subject: [PATCH] feat(#157) "Ease the installation of distance Oracle"
 (nodes/rust/duniter-v2s!214)

* feat(#157): documentation with docker-compose.yml

* fix(#157): review: apt-get clean

* fix(#157): review: either `--` launch or loop

* feat(#157): output example

* feat(#157): Dockerfile update

* feat(#157): add logs for oracle
---
 client/distance/src/lib.rs        | 15 +++++++++++++--
 distance-oracle/README.md         | 11 ++++++++++-
 distance-oracle/src/api.rs        | 16 +++++++++++++---
 distance-oracle/src/lib.rs        | 23 +++++++++++++++++------
 docker-compose.yml                | 13 +++++++++++++
 docker/Dockerfile                 |  7 +++++++
 docker/docker-distance-entrypoint | 21 +++++++++++++++++++++
 7 files changed, 94 insertions(+), 12 deletions(-)
 create mode 100755 docker/docker-distance-entrypoint

diff --git a/client/distance/src/lib.rs b/client/distance/src/lib.rs
index 821be0c6d..f2b29cbe9 100644
--- a/client/distance/src/lib.rs
+++ b/client/distance/src/lib.rs
@@ -43,6 +43,7 @@ where
     IdtyIndex: Decode + Encode + PartialEq + TypeInfo,
 {
     let &[owner_key] = owner_keys else {
+        log::error!("🧙 [distance oracle] More than one Babe owner key: oracle cannot work");
         return Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(None));
     };
     let owner_key = sp_runtime::AccountId32::new(owner_key.0);
@@ -82,23 +83,33 @@ where
 
     // Have we already published a result for this session?
     if published_results.evaluators.contains(&owner_key) {
+        log::debug!("🧙 [distance oracle] Already published a result for this session");
         return Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(None));
     }
 
     // Read evaluation result from file, if it exists
+    log::debug!(
+        "🧙 [distance oracle] Reading evaluation result from file {:?}",
+        distance_dir.clone().join(session_index.to_string())
+    );
     let evaluation_result = match std::fs::read(distance_dir.join(session_index.to_string())) {
         Ok(data) => data,
         Err(e) => {
             match e.kind() {
-                std::io::ErrorKind::NotFound => {}
+                std::io::ErrorKind::NotFound => {
+                    log::debug!("🧙 [distance oracle] Evaluation result file not found");
+                }
                 _ => {
-                    log::error!("Cannot read distance evaluation result file: {e:?}");
+                    log::error!(
+                        "🧙 [distance oracle] Cannot read distance evaluation result file: {e:?}"
+                    );
                 }
             }
             return Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(None));
         }
     };
 
+    log::info!("🧙 [distance oracle] Providing evaluation result");
     Ok(sp_distance::InherentDataProvider::<IdtyIndex>::new(Some(
         sp_distance::ComputationResult::decode(&mut evaluation_result.as_slice()).unwrap(),
     )))
diff --git a/distance-oracle/README.md b/distance-oracle/README.md
index 5a4233827..cff2d0546 100644
--- a/distance-oracle/README.md
+++ b/distance-oracle/README.md
@@ -17,4 +17,13 @@ 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
\ No newline at end of file
+- **/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
diff --git a/distance-oracle/src/api.rs b/distance-oracle/src/api.rs
index 8a7699811..0ddcaba1d 100644
--- a/distance-oracle/src/api.rs
+++ b/distance-oracle/src/api.rs
@@ -15,6 +15,7 @@
 // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
 
 use crate::runtime;
+use log::debug;
 
 use sp_core::H256;
 use subxt::storage::StorageKey;
@@ -52,9 +53,18 @@ pub async fn current_pool(
         .storage()
         .at(parent_hash)
         .fetch(&match current_session % 3 {
-            0 => runtime::storage().distance().evaluation_pool1(),
-            1 => runtime::storage().distance().evaluation_pool2(),
-            2 => runtime::storage().distance().evaluation_pool0(),
+            0 => {
+                debug!("Looking at Pool1 for session {}", current_session);
+                runtime::storage().distance().evaluation_pool1()
+            }
+            1 => {
+                debug!("Looking at Pool2 for session {}", current_session);
+                runtime::storage().distance().evaluation_pool2()
+            }
+            2 => {
+                debug!("Looking at Pool0 for session {}", current_session);
+                runtime::storage().distance().evaluation_pool0()
+            }
             _ => unreachable!("n%3<3"),
         })
         .await
diff --git a/distance-oracle/src/lib.rs b/distance-oracle/src/lib.rs
index 738e43bbc..bc8c432ba 100644
--- a/distance-oracle/src/lib.rs
+++ b/distance-oracle/src/lib.rs
@@ -28,7 +28,7 @@ use api::{AccountId, IdtyIndex};
 
 use codec::Encode;
 use fnv::{FnvHashMap, FnvHashSet};
-use log::{debug, error, warn};
+use log::{debug, error, info, warn};
 use rayon::iter::IntoParallelRefIterator;
 use rayon::iter::ParallelIterator;
 use std::io::Write;
@@ -93,6 +93,7 @@ pub async fn run_and_save(client: &api::Client, settings: Settings) {
         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)
@@ -155,13 +156,13 @@ pub async fn run(
     // Fetch the pending identities
     let Some(evaluation_pool) = api::current_pool(client, parent_hash, current_session).await
     else {
-        debug!("Nothing to do: Pool does not exist");
+        info!("Nothing to do: Pool does not exist");
         return None;
     };
 
     // Stop if nothing to evaluate
     if evaluation_pool.evaluations.0.is_empty() {
-        debug!("Nothing to do: Pool is empty");
+        info!("Nothing to do: Pool is empty");
         return None;
     }
 
@@ -172,7 +173,7 @@ pub async fn run(
     if handle_fs {
         // Stop if already evaluated
         if evaluation_result_path.try_exists().unwrap() {
-            debug!("Nothing to do: File already exists");
+            info!("Nothing to do: File already exists");
             return None;
         }
 
@@ -184,6 +185,7 @@ pub async fn run(
         });
     }
 
+    info!("Evaluating distance for session {}", current_session);
     let evaluation_block = api::evaluation_block(client, parent_hash).await;
 
     // member idty -> issued certs
@@ -292,6 +294,7 @@ fn distance_rule(
     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 =
@@ -304,12 +307,20 @@ fn distance_rule(
         &mut known_idties,
         depth,
     );
-    if referees.contains_key(&idty) {
+    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
 }
diff --git a/docker-compose.yml b/docker-compose.yml
index 5a575fc10..6fbb11bc9 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -22,6 +22,19 @@ services:
       DUNITER_CHAIN_NAME: "gdev"
     volumes:
       - duniter-local-data:/var/lib/duniter
+  distance-oracle:
+    container_name: distance-oracle
+    # choose the version of the image here
+    image: duniter/duniter-v2s:latest
+    entrypoint: docker-distance-entrypoint
+    environment:
+      ORACLE_RPC_URL: "ws://duniter-v2s:9944"
+      ORACLE_RESULT_DIR: "/var/lib/duniter/chains/gdev/distance/"
+      ORACLE_EXECUTION_INTERVAL: "10"
+      ORACLE_MAX_DEPTH: "5"
+      ORACLE_LOG_LEVEL: "info"
+    volumes:
+      - duniter-local-data:/var/lib/duniter
 
 volumes:
   duniter-local-data:
diff --git a/docker/Dockerfile b/docker/Dockerfile
index b35823409..aa49422db 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -84,6 +84,12 @@ 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
@@ -96,3 +102,4 @@ USER duniter
 # Intall
 COPY --from=build /root/build /usr/local/bin/
 COPY docker/docker-entrypoint /usr/local/bin/
+COPY docker/docker-distance-entrypoint /usr/local/bin/
diff --git a/docker/docker-distance-entrypoint b/docker/docker-distance-entrypoint
new file mode 100755
index 000000000..3298bcdab
--- /dev/null
+++ b/docker/docker-distance-entrypoint
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# Custom startup if a first argument is present and is equal to '--'
+# then we just run duniter with the provided arguments (but the '--')
+# without applying all the automated configuration below
+if [ "$1" = -- ]; then
+  shift
+  distance-oracle "$@"
+else
+  ORACLE_RESULT_DIR="${ORACLE_RESULT_DIR:-/distance}"
+  ORACLE_EXECUTION_INTERVAL="${ORACLE_EXECUTION_INTERVAL:-1800}"
+  ORACLE_MAX_DEPTH="${ORACLE_MAX_DEPTH:-5}"
+  ORACLE_RPC_URL="${ORACLE_RPC_URL:-ws://127.0.0.1:9944}"
+  ORACLE_LOG_LEVEL="${ORACLE_LOG_LEVEL:-info}"
+
+  while [ true ]; do
+    distance-oracle -d "$ORACLE_RESULT_DIR" -D "$ORACLE_MAX_DEPTH" -u "$ORACLE_RPC_URL" -l "$ORACLE_LOG_LEVEL"
+    echo "Waiting $ORACLE_EXECUTION_INTERVAL seconds before next execution..."
+    sleep $ORACLE_EXECUTION_INTERVAL
+  done
+fi
-- 
GitLab