Skip to content
Snippets Groups Projects
mod.rs 6.1 KiB
Newer Older
// Copyright 2021 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/>.

#![allow(clippy::enum_variant_names, dead_code, unused_imports)]

pub mod balances;

#[subxt::subxt(runtime_metadata_path = "../resources/metadata.scale")]
pub mod node_runtime {}

use serde_json::Value;
use sp_keyring::AccountKeyring;
use std::process::Command;
use subxt::{ClientBuilder, DefaultConfig, DefaultExtra};

pub type Api = node_runtime::RuntimeApi<DefaultConfig, DefaultExtra<DefaultConfig>>;
pub type Client = subxt::Client<DefaultConfig>;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;

pub const SUDO_ACCOUNT: AccountKeyring = AccountKeyring::Alice;

pub struct Process(std::process::Child);

impl Drop for Process {
    fn drop(&mut self) {
        self.0.kill().expect("node already down");
    }
}

struct FullNode {
    process: Process,
    p2p_port: u16,
    ws_port: u16,
}

pub async fn spawn_node() -> (Api, Client, Process) {
    let duniter_binary_path = std::env::var("DUNITER_BINARY_PATH")
        .unwrap_or_else(|_| "../target/debug/duniter".to_owned());
    let FullNode {
        process,
        p2p_port: _,
        ws_port,
    } = spawn_full_node(
        &duniter_binary_path,
        &["--dev", "--execution=Native", "--sealing=manual"],
    );
    let client = ClientBuilder::new()
        .set_url(format!("ws://127.0.0.1:{}", ws_port))
        .build()
        .await
        .expect("fail to connect to node");
    let api = client.clone().to_runtime_api::<Api>();

    (api, client, process)
}

pub async fn create_empty_block(client: &Client) -> Result<()> {
    // Create an empty block
    let _: Value = client
        .rpc()
        .client
        .request(
            "engine_createBlock",
            &[Value::Bool(true), Value::Bool(false), Value::Null],
        )
        .await?;

    Ok(())
pub async fn create_block_with_extrinsic(
    client: &Client,
    extrinsic: subxt::UncheckedExtrinsic<DefaultConfig, DefaultExtra<DefaultConfig>>,
) -> Result<subxt::TransactionEvents<DefaultConfig>> {
    // Get a hash of the extrinsic (we'll need this later).
    use subxt::sp_runtime::traits::Hash as _;
    let ext_hash = <DefaultConfig as subxt::Config>::Hashing::hash_of(&extrinsic);
    // Submit and watch for transaction progress.
    let sub = client.rpc().watch_extrinsic(extrinsic).await?;
    let watcher = subxt::TransactionProgress::new(sub, client, ext_hash);

    // Create a non-empty block
    let _: Value = client
        .rpc()
        .client
        .request(
            "engine_createBlock",
            &[Value::Bool(false), Value::Bool(false), Value::Null],
        )
        .await?;

    // Get extrinsic events
    watcher
        .wait_for_in_block()
        .await?
        .fetch_events()
        .await
        .map_err(Into::into)

fn spawn_full_node(duniter_binary_path: &str, args: &[&str]) -> FullNode {
    let p2p_port = portpicker::pick_unused_port().expect("No ports free");
    let rpc_port = portpicker::pick_unused_port().expect("No ports free");
    let ws_port = portpicker::pick_unused_port().expect("No ports free");
    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");
    let process = Process(
        Command::new(duniter_binary_path)
            .args(
                [
                    "--no-telemetry",
                    "--no-prometheus",
                    "--tmp",
                    "--port",
                    &p2p_port.to_string(),
                    "--rpc-port",
                    &rpc_port.to_string(),
                    "--ws-port",
                    &ws_port.to_string(),
                ]
                .iter()
                .chain(args),
            )
            .stdout(std::process::Stdio::null())
            .stderr(log_file)
            .spawn()
            .expect("failed to spawn node"),
    );

    let timeout =
        if let Ok(duration_string) = std::env::var("DUNITER_END2END_TESTS_SPAWN_NODE_TIMEOUT") {
            duration_string.parse().unwrap_or(4)
        } else {
            4
        };

    wait_until_log_line(
        "***** Duniter has fully started *****",
        &log_file_path,
        std::time::Duration::from_secs(timeout),
    );

    FullNode {
        process,
        p2p_port,
        ws_port,
    }
}

fn wait_until_log_line(expected_log_line: &str, log_file_path: &str, timeout: std::time::Duration) {
    let (tx, rx) = std::sync::mpsc::channel();
    let mut watcher = notify::watcher(tx, std::time::Duration::from_millis(100)).unwrap();
    use notify::Watcher as _;
    watcher
        .watch(&log_file_path, notify::RecursiveMode::NonRecursive)
        .unwrap();

    let mut pos = 0;
    loop {
        match rx.recv_timeout(timeout) {
            Ok(notify::DebouncedEvent::Write(_)) => {
                let mut file = std::fs::File::open(&log_file_path).unwrap();
                file.seek(std::io::SeekFrom::Start(pos)).unwrap();
                pos = file.metadata().unwrap().len();
                let reader = std::io::BufReader::new(file);

                for line in reader.lines() {
                    if line.expect("fail to read line").contains(expected_log_line) {
                        return;
                    }
                }
            }
            Ok(_) => {}
            Err(err) => {
                eprintln!("Error: {:?}", err);
                std::process::exit(1);
            }
        }
    }
}