Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI status command #214

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion trustchain-cli/src/bin/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use trustchain_api::{
api::{TrustchainDIDAPI, TrustchainDataAPI, TrustchainVCAPI},
TrustchainAPI,
};
use trustchain_cli::config::cli_config;
use trustchain_cli::{config::cli_config, print_status};
use trustchain_core::{
utils::extract_keys,
vc::{CredentialError, DataCredentialError},
Expand Down Expand Up @@ -44,6 +44,13 @@ fn cli() -> Command {
.subcommand_required(true)
.arg_required_else_help(true)
.allow_external_subcommands(true)
.subcommand(
Command::new("status")
.about("Trustchain node status.")
.subcommand_required(false)
.arg_required_else_help(false)
.allow_external_subcommands(false)
)
.subcommand(
Command::new("did")
.about("DID functionality: create, attest, resolve, verify.")
Expand Down Expand Up @@ -189,6 +196,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let resolver = verifier.resolver();
let mut context_loader = ContextLoader::default();
match matches.subcommand() {
Some(("status", _)) => {
print_status().await;
}
Some(("did", sub_matches)) => {
match sub_matches.subcommand() {
Some(("create", sub_matches)) => {
Expand Down
56 changes: 56 additions & 0 deletions trustchain-cli/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,57 @@
use config::cli_config;
use trustchain_ion::{
utils::{bitcoind_status, ion_ok, ipfs_ok, mongodb_ok, BitcoindStatus},
TrustchainBitcoinError,
};

pub mod config;

/// Prints the current status of ION and its dependencies.
pub async fn print_status() {
let str = "IPFS......... ".to_string();
let msg = Some("IPFS daemon not found");
println!("{}", status_str(str, ipfs_ok().await, msg));

// Check bitcoind status to determine network (mainnet or testnet).
let bitcoind_status = bitcoind_status().await;
let bitcoin_str = "Bitcoin...... ".to_string();
match bitcoind_status {
BitcoindStatus::Ok(network) => {
let mongo_str = "MongoDB...... ".to_string();
let msg = Some("Mongo daemon not found");
println!("{}", status_str(mongo_str, mongodb_ok(&network).await, msg));

println!("{}", status_str(bitcoin_str, true, None));

let ion_str = "ION.......... ".to_string();
let msg = Some("ION DID resolution attempt failed");
let is_ok = ion_ok(&network, cli_config().ion_endpoint.port).await;
println!("{}", status_str(ion_str, is_ok, msg));
}
BitcoindStatus::Synching(blocks, headers) => {
let msg = Some(format!("Synching blocks: {}/{}", blocks, headers));
println!("{}", status_str(bitcoin_str, false, msg.as_deref()));
}
BitcoindStatus::Error(e) => {
let msg = match e {
err @ TrustchainBitcoinError::BitcoinCoreRPCError(_) => err.to_string(),
_ => "Bitcoin RPC returned an error".to_string(),
};
println!("{}", status_str(bitcoin_str, false, Some(&msg)));
}
};
}

pub fn status_str(mut str: String, is_ok: bool, details: Option<&str>) -> String {
if is_ok {
str.push_str("✅");
return str;
}
str.push_str("❌");
if let Some(detail) = details {
str.push_str(" [");
str.push_str(detail);
str.push_str("]");
}
str
}
15 changes: 15 additions & 0 deletions trustchain-ion/src/data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
//! Test fixtures for crate.
#![allow(dead_code)]

use crate::utils::BitcoinNetwork;

pub(crate) const SAMPLE_CID: &str = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
pub(crate) const SAMPLE_DID_TESTNET: &str =
"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
pub(crate) const SAMPLE_DID_MAINNET: &str =
"did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w";

pub fn sample_did(network: &BitcoinNetwork) -> String {
match network {
BitcoinNetwork::Mainnet => SAMPLE_DID_MAINNET.to_string(),
BitcoinNetwork::Testnet3 => SAMPLE_DID_TESTNET.to_string(),
}
}

// Note on test fixtures:
//
// This file contains samples of content from the three ION file types written to IPFS:
Expand Down
76 changes: 73 additions & 3 deletions trustchain-ion/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! ION-related utilities.
use crate::data::{sample_did, SAMPLE_CID};
use crate::{
config::ion_config, MONGO_FILTER_OP_INDEX, MONGO_FILTER_TXN_NUMBER, MONGO_FILTER_TXN_TIME,
};
use bitcoin::{BlockHash, BlockHeader, Transaction};
use bitcoincore_rpc::json::GetBlockchainInfoResult;
use bitcoincore_rpc::{bitcoincore_rpc_json::BlockStatsFields, RpcApi};
use chrono::NaiveDate;
use flate2::read::GzDecoder;
Expand All @@ -12,12 +14,14 @@ use mongodb::{bson::doc, options::ClientOptions, Cursor};
use serde_json::{json, Value};
use std::io::Read;
use std::{cmp::Ordering, collections::HashMap};
use trustchain_core::resolver::TrustchainResolver;
use trustchain_core::{utils::get_did_suffix, verifier::VerifierError};

use crate::{
TrustchainBitcoinError, TrustchainIpfsError, TrustchainMongodbError, BITS_KEY,
HASH_PREV_BLOCK_KEY, MERKLE_ROOT_KEY, MONGO_COLLECTION_OPERATIONS, MONGO_CREATE_OPERATION,
MONGO_FILTER_DID_SUFFIX, MONGO_FILTER_TYPE, NONCE_KEY, TIMESTAMP_KEY, VERSION_KEY,
trustchain_resolver, TrustchainBitcoinError, TrustchainIpfsError, TrustchainMongodbError,
BITS_KEY, HASH_PREV_BLOCK_KEY, MERKLE_ROOT_KEY, MONGO_COLLECTION_OPERATIONS,
MONGO_CREATE_OPERATION, MONGO_FILTER_DID_SUFFIX, MONGO_FILTER_TYPE, NONCE_KEY, TIMESTAMP_KEY,
VERSION_KEY,
};

const ION_METHOD_WITH_DELIMITER: &str = "ion:";
Expand Down Expand Up @@ -169,6 +173,18 @@ pub fn rpc_client() -> bitcoincore_rpc::Client {
.unwrap()
}

/// Gets Bitcoin blockchain info via the RPC API.
pub fn blockchain_info(
client: Option<&bitcoincore_rpc::Client>,
) -> Result<GetBlockchainInfoResult, TrustchainBitcoinError> {
// If necessary, construct a Bitcoin RPC client to communicate with the ION Bitcoin node.
if client.is_none() {
let rpc_client = rpc_client();
return blockchain_info(Some(&rpc_client));
};
Ok(client.unwrap().get_blockchain_info()?)
}

/// Gets a Bitcoin block header via the RPC API.
pub fn block_header(
block_hash: &BlockHash,
Expand Down Expand Up @@ -393,6 +409,54 @@ pub fn block_height_range_on_date(
Ok((first_block, last_block))
}

#[derive(Debug, Clone)]
pub enum BitcoinNetwork {
Mainnet,
Testnet3,
}

#[derive(Debug)]
pub enum BitcoindStatus {
Ok(BitcoinNetwork),
Synching(u64, u64),
Error(TrustchainBitcoinError),
}

/// Returns the current status of bitcoind.
pub async fn bitcoind_status() -> BitcoindStatus {
let info = blockchain_info(None);
if info.is_err() {
return BitcoindStatus::Error(info.err().unwrap());
}
let info = info.unwrap();
if info.blocks == info.headers {
if info.chain == "main" {
return BitcoindStatus::Ok(BitcoinNetwork::Mainnet);
}
return BitcoindStatus::Ok(BitcoinNetwork::Testnet3);
}
BitcoindStatus::Synching(info.blocks, info.headers)
}

/// Returns true if the IPFS daemon is running on the expected port.
pub async fn ipfs_ok() -> bool {
query_ipfs(SAMPLE_CID, &IpfsClient::default()).await.is_ok()
}

/// Returns true if the MongoDB daemon is running on the expected port.
pub async fn mongodb_ok(network: &BitcoinNetwork) -> bool {
query_mongodb(get_did_suffix(&sample_did(network)))
.await
.is_ok()
}

/// Returns true if the ION Core microservice is running on the expected port.
pub async fn ion_ok(network: &BitcoinNetwork, ion_port: u16) -> bool {
let resolver = trustchain_resolver(&format!("http://localhost:{}/", ion_port));
let result = resolver.resolve_as_result(&sample_did(network)).await;
result.is_ok()
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -747,4 +811,10 @@ mod tests {
// The last testnet block mined on 2022-10-20 (UTC) was at height 2377519.
assert_eq!(result, (2377360, 2377519));
}

#[tokio::test]
#[ignore = "Integration test requires Bitcoin"]
async fn test_bitcoind_status() {
let _ = bitcoind_status().await;
}
}
Loading