diff --git a/end2end-tests/cucumber-features/distance.feature b/end2end-tests/cucumber-features/distance.feature new file mode 100644 index 0000000000000000000000000000000000000000..9cc18d514ce91ef7d296edfbcb3cab0520b4691c --- /dev/null +++ b/end2end-tests/cucumber-features/distance.feature @@ -0,0 +1,24 @@ +Feature: Distance + + Scenario: Alice certifies Eve + When alice sends 6 ÄžD to dave + When 15 blocks later + When alice creates identity for dave + Then dave identity should be created + Then dave should be certified by alice + When dave confirms his identity with pseudo "dave" + Then dave identity should be confirmed + When bob certifies dave + Then dave should be certified by bob + When alice requests distance evaluation for dave + Then dave should have distance result in 2 sessions + When 30 blocks later + Then dave should have distance result in 1 session + When distance oracle runs + Then dave should have distance result in 1 session + When 30 blocks later + Then dave should have distance result in 0 session + Then dave should have distance ok + When alice validates dave identity + When 3 blocks later + Then dave identity should be validated diff --git a/end2end-tests/tests/common/distance.rs b/end2end-tests/tests/common/distance.rs new file mode 100644 index 0000000000000000000000000000000000000000..6c792700d3140c150fbdb218b2aeb93282bc0cac --- /dev/null +++ b/end2end-tests/tests/common/distance.rs @@ -0,0 +1,41 @@ +// 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 super::gdev; +use super::gdev::runtime_types::pallet_identity; +use super::*; +use crate::DuniterWorld; +use sp_keyring::AccountKeyring; +use subxt::tx::PairSigner; + +pub async fn request_evaluation(client: &Client, from: AccountKeyring, to: u32) -> Result<()> { + let from = PairSigner::new(from.pair()); + + let _events = create_block_with_extrinsic( + client, + client + .tx() + .create_signed( + &gdev::tx().distance().evaluate_distance(to), + &from, + BaseExtrinsicParamsBuilder::new(), + ) + .await?, + ) + .await?; + + Ok(()) +} diff --git a/end2end-tests/tests/common/mod.rs b/end2end-tests/tests/common/mod.rs index 56ccdea5deadfb58b23dc74f2b7a818f245e3f97..2ff94d982b7d57b3dfdf982bb0aa019fa6849ddf 100644 --- a/end2end-tests/tests/common/mod.rs +++ b/end2end-tests/tests/common/mod.rs @@ -18,6 +18,7 @@ pub mod balances; pub mod cert; +pub mod distance; pub mod identity; pub mod oneshot; @@ -85,6 +86,7 @@ impl Process { } } +pub const DISTANCE_ORACLE_LOCAL_PATH: &str = "../target/debug/distance-oracle"; const DUNITER_DOCKER_PATH: &str = "/usr/local/bin/duniter"; const DUNITER_LOCAL_PATH: &str = "../target/debug/duniter"; @@ -94,7 +96,7 @@ struct FullNode { ws_port: u16, } -pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Client, Process) { +pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Client, Process, u16) { println!("maybe_genesis_conf_file={:?}", maybe_genesis_conf_file); let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH").unwrap_or_else(|_| { if std::path::Path::new(DUNITER_DOCKER_PATH).exists() { @@ -117,7 +119,7 @@ pub async fn spawn_node(maybe_genesis_conf_file: Option<PathBuf>) -> (Client, Pr .await .expect("fail to connect to node"); - (client, process) + (client, process, ws_port) } pub async fn create_empty_block(client: &Client) -> Result<()> { @@ -173,6 +175,9 @@ fn spawn_full_node( let log_file_path = format!("duniter-v2s-{}.log", ws_port); let log_file = std::fs::File::create(&log_file_path).expect("fail to create log file"); + // Clean previous data + std::fs::remove_dir_all("/tmp/duniter-cucumber").ok(); + // Command let process = Process( Command::new(duniter_binary_path) @@ -180,13 +185,14 @@ fn spawn_full_node( [ "--no-telemetry", "--no-prometheus", - "--tmp", "--port", &p2p_port.to_string(), "--rpc-port", &rpc_port.to_string(), "--ws-port", &ws_port.to_string(), + "--base-path", + "/tmp/duniter-cucumber", ] .iter() .chain(args), @@ -249,3 +255,22 @@ fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: st } } } + +pub fn spawn_distance_oracle(distance_oracle_binary_path: &str, duniter_rpc_port: u16) { + Command::new(distance_oracle_binary_path) + .args( + [ + "-u", + &format!("ws://127.0.0.1:{duniter_rpc_port}"), + "-d", + "/tmp/duniter-cucumber/chains/gdev/distance", + "-c", + "2", + ] + .iter(), + ) + .spawn() + .expect("failed to spawn distance oracle") + .wait() + .unwrap(); +} diff --git a/end2end-tests/tests/cucumber_tests.rs b/end2end-tests/tests/cucumber_tests.rs index 094932d4bfd8ac137e6bae82f74be7f69d379046..21e0a8be539e0fef56bf37c133238c89f35f621d 100644 --- a/end2end-tests/tests/cucumber_tests.rs +++ b/end2end-tests/tests/cucumber_tests.rs @@ -120,12 +120,17 @@ impl World for DuniterWorld { struct DuniterWorldInner { client: Client, process: Process, + ws_port: u16, } impl DuniterWorldInner { async fn new(maybe_genesis_conf_file: Option<PathBuf>) -> Self { - let (client, process) = spawn_node(maybe_genesis_conf_file).await; - DuniterWorldInner { client, process } + let (client, process, ws_port) = spawn_node(maybe_genesis_conf_file).await; + DuniterWorldInner { + client, + process, + ws_port, + } } fn kill(&mut self) { self.process.kill(); @@ -322,6 +327,28 @@ async fn validate_identity(world: &mut DuniterWorld, from: String, to: String) - common::identity::validate_identity(world.client(), from, to).await } +#[when(regex = r#"([a-zA-Z]+) requests distance evaluation for ([a-zA-Z]+)"#)] +async fn request_distance_evaluation( + world: &mut DuniterWorld, + from: String, + to: String, +) -> Result<()> { + // input names to keyrings + let from = AccountKeyring::from_str(&from).expect("unknown from"); + let to: u32 = common::identity::get_identity_index(world, to).await?; + + common::distance::request_evaluation(world.client(), from, to).await +} + +#[when(regex = r#"distance oracle runs"#)] +async fn run_distance_oracle(world: &mut DuniterWorld) -> Result<()> { + common::spawn_distance_oracle( + common::DISTANCE_ORACLE_LOCAL_PATH, + world.inner.as_ref().unwrap().ws_port, + ); + Ok(()) +} + // ===== then ==== #[then(regex = r"([a-zA-Z]+) should have (\d+) (ÄžD|cÄžD)")] @@ -437,6 +464,66 @@ async fn should_be_certified_by( } } +#[then(regex = r"([a-zA-Z]+) should have distance result in (\d+) sessions?")] +async fn should_have_distance_result_in_sessions( + world: &mut DuniterWorld, + who: String, + sessions: u32, +) -> Result<()> { + assert!(sessions < 3, "Session number must be < 3"); + + let who = AccountKeyring::from_str(&who).unwrap().to_account_id(); + + let idty_id = world + .read(&gdev::storage().identity().identity_index_of(&who)) + .await? + .unwrap(); + + let current_session = world + .read(&gdev::storage().session().current_index()) + .await? + .unwrap_or_default(); + + let pool = world + .read(&match (current_session + sessions) % 3 { + 0 => gdev::storage().distance().evaluation_pool0(), + 1 => gdev::storage().distance().evaluation_pool1(), + 2 => gdev::storage().distance().evaluation_pool2(), + _ => unreachable!("n%3<3"), + }) + .await + .unwrap() + .ok_or_else(|| anyhow::anyhow!("given pool is empty"))?; + + for (sample_idty, _) in pool.0 .0 { + if sample_idty == idty_id { + return Ok(()); + } + } + + Err(anyhow::anyhow!("no evaluation in given pool").into()) +} + +#[then(regex = r"([a-zA-Z]+) should have distance ok")] +async fn should_have_distance_ok(world: &mut DuniterWorld, who: String) -> Result<()> { + let who = AccountKeyring::from_str(&who).unwrap().to_account_id(); + + let idty_id = world + .read(&gdev::storage().identity().identity_index_of(&who)) + .await? + .unwrap(); + + if world + .read(&gdev::storage().distance().distance_ok_identities(&idty_id)) + .await? + .unwrap() + { + Ok(()) + } else { + Err(anyhow::anyhow!("no evaluation in given pool").into()) + } +} + use gdev::runtime_types::pallet_identity::types::IdtyStatus; // status from string