From 120bb27b2ad47e97fb48057a7c1f69a1d7e61395 Mon Sep 17 00:00:00 2001 From: Tim Hobson Date: Thu, 31 Oct 2024 22:15:25 +0000 Subject: [PATCH 1/5] Add status command to CLI --- trustchain-cli/src/bin/main.rs | 13 +++++- trustchain-cli/src/lib.rs | 60 ++++++++++++++++++++++++++ trustchain-ion/src/data.rs | 6 +++ trustchain-ion/src/utils.rs | 79 ++++++++++++++++++++++++++++++++-- 4 files changed, 154 insertions(+), 4 deletions(-) diff --git a/trustchain-cli/src/bin/main.rs b/trustchain-cli/src/bin/main.rs index ae1c1432..3e70f86a 100644 --- a/trustchain-cli/src/bin/main.rs +++ b/trustchain-cli/src/bin/main.rs @@ -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}, @@ -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.") @@ -189,6 +196,10 @@ async fn main() -> Result<(), Box> { let resolver = verifier.resolver(); let mut context_loader = ContextLoader::default(); match matches.subcommand() { + Some(("status", _)) => { + // TODO: update the trustchain-api crate. + print_status().await; + } Some(("did", sub_matches)) => { match sub_matches.subcommand() { Some(("create", sub_matches)) => { diff --git a/trustchain-cli/src/lib.rs b/trustchain-cli/src/lib.rs index ef68c369..110136b6 100644 --- a/trustchain-cli/src/lib.rs +++ b/trustchain-cli/src/lib.rs @@ -1 +1,61 @@ +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 bitcoind_status = bitcoind_status().await; + let mut is_mainnet = false; + if let BitcoindStatus::Ok(x) = bitcoind_status { + is_mainnet = x; + } + + let str = "IPFS......... ".to_string(); + let msg = Some("IPFS daemon not found"); + println!("{}", status_str(str, ipfs_ok().await, msg)); + + let str = "MongoDB...... ".to_string(); + let msg = Some("Mongo daemon not found"); + println!("{}", status_str(str, mongodb_ok(is_mainnet).await, msg)); + + let str = "Bitcoin...... ".to_string(); + match bitcoind_status { + BitcoindStatus::Ok(_) => { + println!("{}", status_str(str, true, None)); + } + BitcoindStatus::Synching(blocks, headers) => { + let msg = Some(format!("Synching blocks: {}/{}", blocks, headers)); + println!("{}", status_str(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(str, false, Some(&msg))); + } + }; + + let str = "ION.......... ".to_string(); + let msg = Some("ION DID resolution attempt failed"); + println!("{}", status_str(str, ion_ok(is_mainnet).await, msg)); + + // TODO: check trustchain-http server status (report only if positive). +} + +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 +} diff --git a/trustchain-ion/src/data.rs b/trustchain-ion/src/data.rs index ce3f29c9..1b6a0ade 100644 --- a/trustchain-ion/src/data.rs +++ b/trustchain-ion/src/data.rs @@ -1,6 +1,12 @@ //! Test fixtures for crate. #![allow(dead_code)] +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"; + // Note on test fixtures: // // This file contains samples of content from the three ION file types written to IPFS: diff --git a/trustchain-ion/src/utils.rs b/trustchain-ion/src/utils.rs index 41c1bc3f..74fc71b0 100644 --- a/trustchain-ion/src/utils.rs +++ b/trustchain-ion/src/utils.rs @@ -1,8 +1,10 @@ //! ION-related utilities. +use crate::data::{SAMPLE_CID, SAMPLE_DID_MAINNET, SAMPLE_DID_TESTNET}; 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; @@ -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:"; @@ -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 { + // 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, @@ -393,6 +409,57 @@ pub fn block_height_range_on_date( Ok((first_block, last_block)) } +#[derive(Debug)] +pub enum BitcoindStatus { + Ok(bool), + 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 { + return BitcoindStatus::Ok(info.chain == "main"); + } + 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(is_mainnet: bool) -> bool { + query_mongodb(get_did_suffix(&sample_did(is_mainnet))) + .await + .is_ok() +} + +// pub async fn is_mainnet() -> Result { +// let info = blockchain_info(None)?; +// Ok(info.chain == "main") +// } +pub fn sample_did(is_mainnet: bool) -> String { + match is_mainnet { + true => SAMPLE_DID_MAINNET.to_string(), + false => SAMPLE_DID_TESTNET.to_string(), + } +} + +/// Returns true if the ION Core microservice is running on the expected port. +pub async fn ion_ok(is_mainnet: bool) -> bool { + // TODO: get ion_port from trustchain_config.toml + let resolver = trustchain_resolver("http://localhost:3000/"); + let result = resolver.resolve_as_result(&sample_did(is_mainnet)).await; + result.is_ok() +} + #[cfg(test)] mod tests { use super::*; @@ -747,4 +814,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; + } } From 1d3920e9cc37338d9ac5d2211fbab99a29c10978 Mon Sep 17 00:00:00 2001 From: Tim Hobson Date: Fri, 1 Nov 2024 11:12:47 +0000 Subject: [PATCH 2/5] Get ION port from CLI config --- trustchain-cli/src/lib.rs | 4 +++- trustchain-ion/src/utils.rs | 5 ++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/trustchain-cli/src/lib.rs b/trustchain-cli/src/lib.rs index 110136b6..40479de9 100644 --- a/trustchain-cli/src/lib.rs +++ b/trustchain-cli/src/lib.rs @@ -1,3 +1,4 @@ +use config::cli_config; use trustchain_ion::{ utils::{bitcoind_status, ion_ok, ipfs_ok, mongodb_ok, BitcoindStatus}, TrustchainBitcoinError, @@ -41,7 +42,8 @@ pub async fn print_status() { let str = "ION.......... ".to_string(); let msg = Some("ION DID resolution attempt failed"); - println!("{}", status_str(str, ion_ok(is_mainnet).await, msg)); + let is_ok = ion_ok(is_mainnet, cli_config().ion_endpoint.port).await; + println!("{}", status_str(str, is_ok, msg)); // TODO: check trustchain-http server status (report only if positive). } diff --git a/trustchain-ion/src/utils.rs b/trustchain-ion/src/utils.rs index 74fc71b0..e28fbe2d 100644 --- a/trustchain-ion/src/utils.rs +++ b/trustchain-ion/src/utils.rs @@ -453,9 +453,8 @@ pub fn sample_did(is_mainnet: bool) -> String { } /// Returns true if the ION Core microservice is running on the expected port. -pub async fn ion_ok(is_mainnet: bool) -> bool { - // TODO: get ion_port from trustchain_config.toml - let resolver = trustchain_resolver("http://localhost:3000/"); +pub async fn ion_ok(is_mainnet: bool, ion_port: u16) -> bool { + let resolver = trustchain_resolver(&format!("http://localhost:{}/", ion_port)); let result = resolver.resolve_as_result(&sample_did(is_mainnet)).await; result.is_ok() } From 44b0ce5fff9349bf3e62475b16475c491d64623a Mon Sep 17 00:00:00 2001 From: Tim Hobson Date: Tue, 5 Nov 2024 15:09:54 +0000 Subject: [PATCH 3/5] Move sample_did to trustchain-ion data module --- trustchain-ion/src/data.rs | 7 +++++++ trustchain-ion/src/utils.rs | 13 +------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/trustchain-ion/src/data.rs b/trustchain-ion/src/data.rs index 1b6a0ade..9fec579d 100644 --- a/trustchain-ion/src/data.rs +++ b/trustchain-ion/src/data.rs @@ -7,6 +7,13 @@ pub(crate) const SAMPLE_DID_TESTNET: &str = pub(crate) const SAMPLE_DID_MAINNET: &str = "did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w"; +pub fn sample_did(is_mainnet: bool) -> String { + match is_mainnet { + true => SAMPLE_DID_MAINNET.to_string(), + false => SAMPLE_DID_TESTNET.to_string(), + } +} + // Note on test fixtures: // // This file contains samples of content from the three ION file types written to IPFS: diff --git a/trustchain-ion/src/utils.rs b/trustchain-ion/src/utils.rs index e28fbe2d..e8e73464 100644 --- a/trustchain-ion/src/utils.rs +++ b/trustchain-ion/src/utils.rs @@ -1,5 +1,5 @@ //! ION-related utilities. -use crate::data::{SAMPLE_CID, SAMPLE_DID_MAINNET, SAMPLE_DID_TESTNET}; +use crate::data::{sample_did, SAMPLE_CID}; use crate::{ config::ion_config, MONGO_FILTER_OP_INDEX, MONGO_FILTER_TXN_NUMBER, MONGO_FILTER_TXN_TIME, }; @@ -441,17 +441,6 @@ pub async fn mongodb_ok(is_mainnet: bool) -> bool { .is_ok() } -// pub async fn is_mainnet() -> Result { -// let info = blockchain_info(None)?; -// Ok(info.chain == "main") -// } -pub fn sample_did(is_mainnet: bool) -> String { - match is_mainnet { - true => SAMPLE_DID_MAINNET.to_string(), - false => SAMPLE_DID_TESTNET.to_string(), - } -} - /// Returns true if the ION Core microservice is running on the expected port. pub async fn ion_ok(is_mainnet: bool, ion_port: u16) -> bool { let resolver = trustchain_resolver(&format!("http://localhost:{}/", ion_port)); From 02bdd6dcfc6f32f130585c1a43cb1879b107eb31 Mon Sep 17 00:00:00 2001 From: Tim Hobson Date: Fri, 20 Dec 2024 18:24:50 +0000 Subject: [PATCH 4/5] Add BitcoinNetwork enum --- trustchain-cli/src/lib.rs | 38 ++++++++++++++++--------------------- trustchain-ion/src/data.rs | 10 ++++++---- trustchain-ion/src/utils.rs | 21 ++++++++++++++------ 3 files changed, 37 insertions(+), 32 deletions(-) diff --git a/trustchain-cli/src/lib.rs b/trustchain-cli/src/lib.rs index 40479de9..37a2fdd7 100644 --- a/trustchain-cli/src/lib.rs +++ b/trustchain-cli/src/lib.rs @@ -8,44 +8,38 @@ pub mod config; /// Prints the current status of ION and its dependencies. pub async fn print_status() { - let bitcoind_status = bitcoind_status().await; - let mut is_mainnet = false; - if let BitcoindStatus::Ok(x) = bitcoind_status { - is_mainnet = x; - } - let str = "IPFS......... ".to_string(); let msg = Some("IPFS daemon not found"); println!("{}", status_str(str, ipfs_ok().await, msg)); - let str = "MongoDB...... ".to_string(); - let msg = Some("Mongo daemon not found"); - println!("{}", status_str(str, mongodb_ok(is_mainnet).await, msg)); - - let str = "Bitcoin...... ".to_string(); + // 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(_) => { - println!("{}", status_str(str, true, None)); + 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(str, false, msg.as_deref())); + 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(str, false, Some(&msg))); + println!("{}", status_str(bitcoin_str, false, Some(&msg))); } }; - - let str = "ION.......... ".to_string(); - let msg = Some("ION DID resolution attempt failed"); - let is_ok = ion_ok(is_mainnet, cli_config().ion_endpoint.port).await; - println!("{}", status_str(str, is_ok, msg)); - - // TODO: check trustchain-http server status (report only if positive). } pub fn status_str(mut str: String, is_ok: bool, details: Option<&str>) -> String { diff --git a/trustchain-ion/src/data.rs b/trustchain-ion/src/data.rs index 9fec579d..f181e3eb 100644 --- a/trustchain-ion/src/data.rs +++ b/trustchain-ion/src/data.rs @@ -1,16 +1,18 @@ //! 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(is_mainnet: bool) -> String { - match is_mainnet { - true => SAMPLE_DID_MAINNET.to_string(), - false => SAMPLE_DID_TESTNET.to_string(), +pub fn sample_did(network: &BitcoinNetwork) -> String { + match network { + BitcoinNetwork::Mainnet => SAMPLE_DID_MAINNET.to_string(), + BitcoinNetwork::Testnet3 => SAMPLE_DID_TESTNET.to_string(), } } diff --git a/trustchain-ion/src/utils.rs b/trustchain-ion/src/utils.rs index e8e73464..cd7f9b06 100644 --- a/trustchain-ion/src/utils.rs +++ b/trustchain-ion/src/utils.rs @@ -409,9 +409,15 @@ 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(bool), + Ok(BitcoinNetwork), Synching(u64, u64), Error(TrustchainBitcoinError), } @@ -424,7 +430,10 @@ pub async fn bitcoind_status() -> BitcoindStatus { } let info = info.unwrap(); if info.blocks == info.headers { - return BitcoindStatus::Ok(info.chain == "main"); + if info.chain == "main" { + return BitcoindStatus::Ok(BitcoinNetwork::Mainnet); + } + return BitcoindStatus::Ok(BitcoinNetwork::Testnet3); } BitcoindStatus::Synching(info.blocks, info.headers) } @@ -435,16 +444,16 @@ pub async fn ipfs_ok() -> bool { } /// Returns true if the MongoDB daemon is running on the expected port. -pub async fn mongodb_ok(is_mainnet: bool) -> bool { - query_mongodb(get_did_suffix(&sample_did(is_mainnet))) +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(is_mainnet: bool, ion_port: u16) -> bool { +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(is_mainnet)).await; + let result = resolver.resolve_as_result(&sample_did(network)).await; result.is_ok() } From b22a53f6c7d56a5e9af39e07fb2e7072e82a543b Mon Sep 17 00:00:00 2001 From: Tim Hobson Date: Fri, 20 Dec 2024 19:23:54 +0000 Subject: [PATCH 5/5] Remove obsolete comment --- trustchain-cli/src/bin/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/trustchain-cli/src/bin/main.rs b/trustchain-cli/src/bin/main.rs index 3e70f86a..74f9d21f 100644 --- a/trustchain-cli/src/bin/main.rs +++ b/trustchain-cli/src/bin/main.rs @@ -197,7 +197,6 @@ async fn main() -> Result<(), Box> { let mut context_loader = ContextLoader::default(); match matches.subcommand() { Some(("status", _)) => { - // TODO: update the trustchain-api crate. print_status().await; } Some(("did", sub_matches)) => {