Skip to content

Commit 30d18b2

Browse files
authored
Merge pull request #214 from alan-turing-institute/206-cli-status
CLI status command
2 parents 4897f6c + d5eb588 commit 30d18b2

File tree

5 files changed

+203
-13
lines changed

5 files changed

+203
-13
lines changed

crates/trustchain-cli/src/bin/main.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use trustchain_api::{
1313
api::{TrustchainDIDAPI, TrustchainDataAPI, TrustchainVCAPI},
1414
TrustchainAPI,
1515
};
16-
use trustchain_cli::config::cli_config;
16+
use trustchain_cli::{config::cli_config, print_status};
1717
use trustchain_core::{
1818
utils::extract_keys,
1919
vc::{CredentialError, DataCredentialError},
@@ -44,6 +44,13 @@ fn cli() -> Command {
4444
.subcommand_required(true)
4545
.arg_required_else_help(true)
4646
.allow_external_subcommands(true)
47+
.subcommand(
48+
Command::new("status")
49+
.about("Trustchain node status.")
50+
.subcommand_required(false)
51+
.arg_required_else_help(false)
52+
.allow_external_subcommands(false)
53+
)
4754
.subcommand(
4855
Command::new("did")
4956
.about("DID functionality: create, attest, resolve, verify.")
@@ -125,7 +132,7 @@ fn cli() -> Command {
125132
)
126133
.subcommand(
127134
Command::new("cr")
128-
.about("Challenge-response functionality for attestation challenge response process (identity and content challenge-response).")
135+
.about("Challenge-response functionality for downstream DID attestation.")
129136
.subcommand_required(true)
130137
.arg_required_else_help(true)
131138
.allow_external_subcommands(true)
@@ -189,6 +196,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
189196
let resolver = verifier.resolver();
190197
let mut context_loader = ContextLoader::default();
191198
match matches.subcommand() {
199+
Some(("status", _)) => {
200+
print_status().await;
201+
}
192202
Some(("did", sub_matches)) => {
193203
match sub_matches.subcommand() {
194204
Some(("create", sub_matches)) => {

crates/trustchain-cli/src/lib.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,71 @@
1+
use config::cli_config;
2+
use trustchain_ion::{
3+
utils::{bitcoind_status, ion_ok, ipfs_ok, mongodb_ok, BitcoindStatus},
4+
TrustchainBitcoinError,
5+
};
6+
17
pub mod config;
8+
9+
/// Prints the current status of ION and its dependencies.
10+
pub async fn print_status() {
11+
let str = "IPFS......... ".to_string();
12+
let msg = Some("IPFS daemon not found");
13+
println!("{}", status_str(str, ipfs_ok().await, msg));
14+
15+
// Check bitcoind status to determine network (mainnet or testnet).
16+
let bitcoind_status = bitcoind_status().await;
17+
let bitcoin_str = "Bitcoin...... ".to_string();
18+
match bitcoind_status {
19+
BitcoindStatus::Ok(network) => {
20+
let mongo_str = "MongoDB...... ".to_string();
21+
let msg = Some("Mongo daemon not found");
22+
println!("{}", status_str(mongo_str, mongodb_ok(&network).await, msg));
23+
24+
println!("{}", status_str(bitcoin_str, true, None));
25+
26+
let ion_str = "ION.......... ".to_string();
27+
let msg = Some("ION DID resolution attempt failed");
28+
let is_ok = ion_ok(&network, cli_config().ion_endpoint.port).await;
29+
println!("{}", status_str(ion_str, is_ok, msg));
30+
}
31+
BitcoindStatus::Synching(blocks, headers) => {
32+
let msg = Some(format!("Synching blocks: {}/{}", blocks, headers));
33+
println!("{}", status_str(bitcoin_str, false, msg.as_deref()));
34+
}
35+
BitcoindStatus::Error(e) => {
36+
let msg = match e {
37+
err @ TrustchainBitcoinError::BitcoinCoreRPCError(_) => err.to_string(),
38+
_ => "Bitcoin RPC returned an error".to_string(),
39+
};
40+
println!("{}", status_str(bitcoin_str, false, Some(&msg)));
41+
}
42+
BitcoindStatus::UnexpectedNetwork(network) => {
43+
let msg = Some(format!(
44+
"Trustchain is not configured for network: {}",
45+
network.to_string()
46+
));
47+
println!("{}", status_str(bitcoin_str, false, msg.as_deref()));
48+
}
49+
BitcoindStatus::UnsupportedNetwork(network) => {
50+
let msg = Some(format!(
51+
"Trustchain does not support network: {}",
52+
network.to_string()
53+
));
54+
println!("{}", status_str(bitcoin_str, false, msg.as_deref()));
55+
}
56+
};
57+
}
58+
59+
pub fn status_str(mut str: String, is_ok: bool, details: Option<&str>) -> String {
60+
if is_ok {
61+
str.push_str("✅");
62+
return str;
63+
}
64+
str.push_str("❌");
65+
if let Some(detail) = details {
66+
str.push_str(" [");
67+
str.push_str(detail);
68+
str.push_str("]");
69+
}
70+
str
71+
}

crates/trustchain-ion/src/data.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
//! Test fixtures for crate.
22
#![allow(dead_code)]
33

4+
use bitcoin::Network;
5+
6+
use crate::TrustchainBitcoinError;
7+
8+
pub(crate) const SAMPLE_CID: &str = "QmRvgZm4J3JSxfk4wRjE2u2Hi2U7VmobYnpqhqH5QP6J97";
9+
pub(crate) const SAMPLE_DID_MAINNET: &str =
10+
"did:ion:EiClkZMDxPKqC9c-umQfTkR8vvZ9JPhl_xLDI9Nfk38w5w";
11+
pub(crate) const SAMPLE_DID_TESTNET3: &str =
12+
"did:ion:test:EiCClfEdkTv_aM3UnBBhlOV89LlGhpQAbfeZLFdFxVFkEg";
13+
pub(crate) const SAMPLE_DID_TESTNET4: &str =
14+
"did:ion:test:EiDnaq8k5I4xGy1NjKZkNgcFwNt1Jm6mLm0TVVes7riyMA";
15+
16+
pub fn sample_did(network: &Network) -> Result<String, TrustchainBitcoinError> {
17+
match network {
18+
Network::Bitcoin => Ok(SAMPLE_DID_MAINNET.to_string()),
19+
Network::Testnet => Ok(SAMPLE_DID_TESTNET3.to_string()),
20+
Network::Testnet4 => Ok(SAMPLE_DID_TESTNET4.to_string()),
21+
_ => Err(TrustchainBitcoinError::UnsupportedNetwork(*network)),
22+
}
23+
}
24+
425
//
5-
// Testnet fixtures
26+
// Testnet3 fixtures
627
//
728

829
// Note on Testnet test fixtures (see below for Testnet4 fixtures):

crates/trustchain-ion/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,9 @@ pub enum TrustchainBitcoinError {
165165
/// Target date precedes start block timestamp or succeeds end block timestamp.
166166
#[error("Target date out of range of block timestamps.")]
167167
TargetDateOutOfRange,
168+
/// Unsupported Bitcoin network.
169+
#[error("Unsupported Bitcoin network: {0}")]
170+
UnsupportedNetwork(Network),
168171
}
169172

170173
// DID

crates/trustchain-ion/src/utils.rs

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
//! ION-related utilities.
2+
use crate::data::{sample_did, SAMPLE_CID};
23
use crate::data::{
34
TESTNET3_TEST_ROOT_PLUS_1_SIGNING_KEY, TESTNET3_TEST_ROOT_PLUS_2_SIGNING_KEYS,
45
TESTNET4_TEST_ROOT_PLUS_1_SIGNING_KEY, TESTNET4_TEST_ROOT_PLUS_2_SIGNING_KEYS,
56
};
67
use crate::{
78
config::ion_config, MONGO_FILTER_OP_INDEX, MONGO_FILTER_TXN_NUMBER, MONGO_FILTER_TXN_TIME,
89
};
9-
use bitcoin::Network;
10-
use bitcoin::{block::Header, blockdata::block::BlockHash, Transaction};
10+
use bitcoin::{block::Header, blockdata::block::BlockHash, Network, Transaction};
11+
use bitcoincore_rpc::json::GetBlockchainInfoResult;
1112
use bitcoincore_rpc::{bitcoincore_rpc_json::BlockStatsFields, RpcApi};
1213
use chrono::NaiveDate;
1314
use flate2::read::GzDecoder;
@@ -23,13 +24,15 @@ use std::path::Path;
2324
use std::sync::Once;
2425
use std::{cmp::Ordering, collections::HashMap};
2526
use trustchain_core::key_manager::{KeyManager, KeyType};
27+
use trustchain_core::resolver::TrustchainResolver;
2628
use trustchain_core::TRUSTCHAIN_DATA;
2729
use trustchain_core::{utils::get_did_suffix, verifier::VerifierError};
2830

2931
use crate::{
30-
TrustchainBitcoinError, TrustchainIpfsError, TrustchainMongodbError, BITS_KEY,
31-
HASH_PREV_BLOCK_KEY, MERKLE_ROOT_KEY, MONGO_COLLECTION_OPERATIONS, MONGO_CREATE_OPERATION,
32-
MONGO_FILTER_DID_SUFFIX, MONGO_FILTER_TYPE, NONCE_KEY, TIMESTAMP_KEY, VERSION_KEY,
32+
trustchain_resolver, TrustchainBitcoinError, TrustchainIpfsError, TrustchainMongodbError,
33+
BITS_KEY, HASH_PREV_BLOCK_KEY, MERKLE_ROOT_KEY, MONGO_COLLECTION_OPERATIONS,
34+
MONGO_CREATE_OPERATION, MONGO_FILTER_DID_SUFFIX, MONGO_FILTER_TYPE, NONCE_KEY, TIMESTAMP_KEY,
35+
VERSION_KEY,
3336
};
3437

3538
const ION_METHOD_WITH_DELIMITER: &str = "ion:";
@@ -286,16 +289,23 @@ pub fn rpc_client() -> bitcoincore_rpc::Client {
286289
.unwrap()
287290
}
288291

289-
/// Gets the Bitcoin chain via the RPC API.
290-
pub fn bitcoin_network(
292+
/// Gets Bitcoin blockchain info via the RPC API.
293+
pub fn blockchain_info(
291294
client: Option<&bitcoincore_rpc::Client>,
292-
) -> Result<Network, TrustchainBitcoinError> {
295+
) -> Result<GetBlockchainInfoResult, TrustchainBitcoinError> {
293296
// If necessary, construct a Bitcoin RPC client to communicate with the ION Bitcoin node.
294297
if client.is_none() {
295298
let rpc_client = rpc_client();
296-
return bitcoin_network(Some(&rpc_client));
299+
return blockchain_info(Some(&rpc_client));
297300
};
298-
Ok(client.unwrap().get_blockchain_info()?.chain)
301+
Ok(client.unwrap().get_blockchain_info()?)
302+
}
303+
304+
/// Gets the Bitcoin chain via the RPC API.
305+
pub fn bitcoin_network(
306+
client: Option<&bitcoincore_rpc::Client>,
307+
) -> Result<Network, TrustchainBitcoinError> {
308+
Ok(blockchain_info(client)?.chain)
299309
}
300310

301311
/// Gets a Bitcoin block header via the RPC API.
@@ -522,6 +532,76 @@ pub fn block_height_range_on_date(
522532
Ok((first_block, last_block))
523533
}
524534

535+
#[derive(Debug)]
536+
pub enum BitcoindStatus {
537+
Ok(Network),
538+
Synching(u64, u64),
539+
UnexpectedNetwork(Network),
540+
UnsupportedNetwork(Network),
541+
Error(TrustchainBitcoinError),
542+
}
543+
544+
/// Returns the current status of bitcoind.
545+
pub async fn bitcoind_status() -> BitcoindStatus {
546+
let info = blockchain_info(None);
547+
if info.is_err() {
548+
return BitcoindStatus::Error(info.err().unwrap());
549+
}
550+
let info = info.unwrap();
551+
if info.blocks != info.headers {
552+
return BitcoindStatus::Synching(info.blocks, info.headers);
553+
}
554+
let ion_core_config = &ion_config().mongo_database_ion_core;
555+
match info.chain {
556+
Network::Bitcoin => {
557+
if ion_core_config.contains("testnet") {
558+
return BitcoindStatus::UnexpectedNetwork(info.chain);
559+
}
560+
BitcoindStatus::Ok(Network::Bitcoin)
561+
}
562+
Network::Testnet => {
563+
if ion_core_config.contains("mainnet") {
564+
return BitcoindStatus::UnexpectedNetwork(info.chain);
565+
}
566+
BitcoindStatus::Ok(Network::Testnet)
567+
}
568+
Network::Testnet4 => {
569+
if ion_core_config.contains("mainnet") {
570+
return BitcoindStatus::UnexpectedNetwork(info.chain);
571+
}
572+
BitcoindStatus::Ok(Network::Testnet4)
573+
}
574+
_ => BitcoindStatus::UnsupportedNetwork(info.chain),
575+
}
576+
}
577+
578+
/// Returns true if the IPFS daemon is running on the expected port.
579+
pub async fn ipfs_ok() -> bool {
580+
query_ipfs(SAMPLE_CID, &IpfsClient::default()).await.is_ok()
581+
}
582+
583+
/// Returns true if the MongoDB daemon is running on the expected port.
584+
pub async fn mongodb_ok(network: &Network) -> bool {
585+
if let Ok(sample_did) = sample_did(network) {
586+
query_mongodb(get_did_suffix(&sample_did)).await.is_ok()
587+
} else {
588+
// If the given Bitcoin network is unsupported, return false.
589+
false
590+
}
591+
}
592+
593+
/// Returns true if the ION Core microservice is running on the expected port.
594+
pub async fn ion_ok(network: &Network, ion_port: u16) -> bool {
595+
let resolver = trustchain_resolver(&format!("http://localhost:{}/", ion_port));
596+
if let Ok(sample_did) = sample_did(network) {
597+
let result = resolver.resolve_as_result(&sample_did).await;
598+
result.is_ok()
599+
} else {
600+
// If the given Bitcoin network is unsupported, return false.
601+
false
602+
}
603+
}
604+
525605
#[cfg(test)]
526606
mod tests {
527607
use super::*;
@@ -1104,4 +1184,10 @@ mod tests {
11041184
}
11051185
}
11061186
}
1187+
1188+
#[tokio::test]
1189+
#[ignore = "Integration test requires Bitcoin"]
1190+
async fn test_bitcoind_status() {
1191+
let _ = bitcoind_status().await;
1192+
}
11071193
}

0 commit comments

Comments
 (0)