Skip to content

Commit

Permalink
feat: add block-info command to neptune-cli
Browse files Browse the repository at this point in the history
This command makes it easy to query and view block details at a glance.

Supporting changes:
 * impl Display for BlockInfo
 * impl Display for BlockSelector
 * impl FromStr for BlockSelector
   * add BlockSelectorParseError
   * add dep on thiserror
 * moved BlockInfo and BlockSelector into their own files
  • Loading branch information
dan-da committed May 6, 2024
1 parent 64c46e8 commit 3e21a69
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 72 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ async-trait = "0.1.77"
async-stream = "0.3.5"
sha3 = "0.10.8"
readonly = "0.2.12"
thiserror = "1.0.59"

[dev-dependencies]
test-strategy = "0.3"
Expand Down
14 changes: 13 additions & 1 deletion src/bin/neptune-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ use std::net::IpAddr;
use std::net::SocketAddr;
use tarpc::{client, context, tokio_serde::formats::Json};

use neptune_core::models::blockchain::block::block_selector::BlockSelector;
use neptune_core::models::state::wallet::wallet_status::WalletStatus;
use neptune_core::rpc_server::{BlockSelector, RPCClient};
use neptune_core::rpc_server::RPCClient;
use std::io::stdout;
use twenty_first::math::digest::Digest;

Expand All @@ -31,6 +32,10 @@ enum Command {
OwnListenAddressForPeers,
OwnInstanceId,
BlockHeight,
BlockInfo {
/// one of: genesis, tip, height/N, digest/hex
block_selector: BlockSelector,
},
Confirmations,
PeerInfo,
AllSanctionedPeers,
Expand Down Expand Up @@ -287,6 +292,13 @@ async fn main() -> Result<()> {
let block_height = client.block_height(ctx).await?;
println!("Block height: {}", block_height)
}
Command::BlockInfo { block_selector } => {
let data = client.block_info(ctx, block_selector).await?;
match data {
Some(block_info) => println!("{}", block_info),
None => println!("Not found"),
}
}
Command::Confirmations => {
let val = client.confirmations(ctx).await?;
match val {
Expand Down
73 changes: 73 additions & 0 deletions src/models/blockchain/block/block_info.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//! BlockInfo is a concise summary of a block intended for human
//! consumption/reporting in block explorers, cli, dashboard, etc.

use super::block_header::TARGET_DIFFICULTY_U32_SIZE;
use crate::models::blockchain::block::block_height::BlockHeight;
use crate::models::blockchain::block::Block;
use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins;
use crate::models::consensus::timestamp::Timestamp;
use crate::prelude::twenty_first;
use serde::{Deserialize, Serialize};
use twenty_first::math::digest::Digest;
use twenty_first::prelude::U32s;

/// Provides summary information about a Block
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BlockInfo {
pub height: BlockHeight,
pub digest: Digest,
pub timestamp: Timestamp,
pub difficulty: U32s<TARGET_DIFFICULTY_U32_SIZE>,
pub num_inputs: usize,
pub num_outputs: usize,
pub num_uncle_blocks: usize,
pub mining_reward: NeptuneCoins,
pub fee: NeptuneCoins,
pub is_genesis: bool,
pub is_tip: bool,
}

// note: this is used by neptune-cli block-info command.
impl std::fmt::Display for BlockInfo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let buf = String::new()
+ &format!("height: {}\n", self.height)
+ &format!("digest: {}\n", self.digest.to_hex())
+ &format!("timestamp: {}\n", self.timestamp.standard_format())
+ &format!("difficulty: {}\n", self.difficulty)
+ &format!("num_inputs: {}\n", self.num_inputs)
+ &format!("num_outputs: {}\n", self.num_outputs)
+ &format!("num_uncle_blocks: {}\n", self.num_uncle_blocks)
+ &format!("mining_reward: {}\n", self.mining_reward)
+ &format!("fee: {}\n", self.fee)
+ &format!("is_genesis: {}\n", self.is_genesis)
+ &format!("is_tip: {}\n", self.is_tip);

write!(f, "{}", buf)
}
}

impl BlockInfo {
pub fn from_block_and_digests(
block: &Block,
genesis_digest: Digest,
tip_digest: Digest,
) -> Self {
let body = block.body();
let header = block.header();
let digest = block.hash();
Self {
digest,
height: header.height,
timestamp: header.timestamp,
difficulty: header.difficulty,
num_inputs: body.transaction.kernel.inputs.len(),
num_outputs: body.transaction.kernel.outputs.len(),
num_uncle_blocks: body.uncle_blocks.len(),
fee: body.transaction.kernel.fee,
mining_reward: crate::Block::get_mining_reward(header.height),
is_genesis: digest == genesis_digest,
is_tip: digest == tip_digest,
}
}
}
115 changes: 115 additions & 0 deletions src/models/blockchain/block/block_selector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! BlockSelector is a helper for querying blocks.
//!
//! The idea is to instantiate a BlockSelector using any of the following as
//! identifier:
//! * A Digest
//! * A BlockHeight
//! * Genesis
//! * Tip
//!
//! Then call BlockSelector::to_digest() to obtain the block's Digest, if it
//! exists.
//!
//! Public API's such as RPCs should accept a BlockSelector rather than a Digest
//! or Height.

use super::block_height::BlockHeight;
use crate::models::state::GlobalState;
use crate::twenty_first::error::FromHexDigestError;
use crate::twenty_first::math::digest::Digest;
use serde::{Deserialize, Serialize};
use std::num::ParseIntError;
use std::str::FromStr;
use thiserror::Error;

/// Provides alternatives for looking up a block.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum BlockSelector {
Digest(Digest), // Identifies block by Digest (hash)
Height(BlockHeight), // Identifies block by Height (count from genesis)
Genesis, // Indicates the genesis block
Tip, // Indicates the latest canonical block
}

/// BlockSelector can be written out as any of:
/// genesis
/// tip
/// height/<N>
/// digest/<hex>
///
/// This is intended to be easy for humans to read and also input, ie suitable
/// for use as CLI argument.
impl std::fmt::Display for BlockSelector {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Digest(d) => write!(f, "digest/{}", d),
Self::Height(h) => write!(f, "height/{}", h),
Self::Genesis => write!(f, "genesis"),
Self::Tip => write!(f, "tip"),
}
}
}

#[derive(Debug, Clone, Error)]
pub enum BlockSelectorParseError {
#[error("Invalid selector {0}. Try genesis or tip")]
InvalidSelector(String),

#[error("Invalid pair selector {0}. Try height/<N> or digest/<hex>")]
InvalidPairSelector(String),

#[error("Wrong selector length {0}. (too many or too few '/')")]
WrongSelectorLength(usize),

#[error("Bad Digest")]
BadDigest(#[from] FromHexDigestError),

#[error("Bad Height")]
BadHeight(#[from] ParseIntError),
}

impl FromStr for BlockSelector {
type Err = BlockSelectorParseError;

// note: this parses the output of impl Display for BlockSelector
// note: this is used by clap parser in neptune-cli for block-info command
// and probably future commands as well.
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('/').collect();
if parts.len() == 1 {
match parts[0] {
"genesis" => Ok(Self::Genesis),
"tip" => Ok(Self::Tip),
other => Err(BlockSelectorParseError::InvalidSelector(other.to_string())),
}
} else if parts.len() == 2 {
match parts[0] {
"digest" => Ok(Self::Digest(Digest::try_from_hex(parts[1])?)),
"height" => Ok(Self::Height(parts[1].parse::<u64>()?.into())),
other => Err(BlockSelectorParseError::InvalidPairSelector(
other.to_string(),
)),
}
} else {
Err(BlockSelectorParseError::WrongSelectorLength(parts.len()))
}
}
}

impl BlockSelector {
/// returns Digest for this selector, if it exists.
pub async fn as_digest(&self, state: &GlobalState) -> Option<Digest> {
match self {
BlockSelector::Digest(d) => Some(*d),
BlockSelector::Height(h) => {
state
.chain
.archival_state()
.block_height_to_canonical_block_digest(*h, state.chain.light_state().hash())
.await
}
BlockSelector::Tip => Some(state.chain.light_state().hash()),
BlockSelector::Genesis => Some(state.chain.archival_state().genesis_block().hash()),
}
}
}
2 changes: 2 additions & 0 deletions src/models/blockchain/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ use twenty_first::util_types::algebraic_hasher::AlgebraicHasher;
pub mod block_body;
pub mod block_header;
pub mod block_height;
pub mod block_info;
pub mod block_kernel;
pub mod block_selector;
pub mod mutator_set_update;
pub mod transfer_block;
pub mod validity;
Expand Down
71 changes: 4 additions & 67 deletions src/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ use tarpc::context;
use tokio::sync::mpsc::error::SendError;
use tracing::{error, info};
use twenty_first::math::digest::Digest;
use twenty_first::prelude::U32s;
use twenty_first::util_types::algebraic_hasher::AlgebraicHasher;

use crate::config_models::network::Network;
use crate::models::blockchain::block::block_header::{BlockHeader, TARGET_DIFFICULTY_U32_SIZE};
use crate::models::blockchain::block::block_header::BlockHeader;
use crate::models::blockchain::block::block_height::BlockHeight;
use crate::models::blockchain::block::Block;
use crate::models::blockchain::block::block_info::BlockInfo;
use crate::models::blockchain::block::block_selector::BlockSelector;
use crate::models::blockchain::shared::Hash;
use crate::models::blockchain::transaction::utxo::Utxo;
use crate::models::channel::RPCServerToMain;
Expand All @@ -29,7 +29,7 @@ use crate::models::peer::PeerInfo;
use crate::models::peer::PeerStanding;
use crate::models::state::wallet::address::generation_address;
use crate::models::state::wallet::wallet_status::WalletStatus;
use crate::models::state::{GlobalState, GlobalStateLock, UtxoReceiverData};
use crate::models::state::{GlobalStateLock, UtxoReceiverData};

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct DashBoardOverviewDataFromClient {
Expand All @@ -52,69 +52,6 @@ pub struct DashBoardOverviewDataFromClient {
pub confirmations: Option<BlockHeight>,
}

/// Provides summary information about a Block
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct BlockInfo {
pub height: BlockHeight,
pub digest: Digest,
pub timestamp: Timestamp,
pub difficulty: U32s<TARGET_DIFFICULTY_U32_SIZE>,
pub num_inputs: usize,
pub num_outputs: usize,
pub num_uncle_blocks: usize,
pub mining_reward: NeptuneCoins,
pub fee: NeptuneCoins,
pub is_genesis: bool,
pub is_tip: bool,
}

impl BlockInfo {
fn from_block_and_digests(block: &Block, genesis_digest: Digest, tip_digest: Digest) -> Self {
let body = block.body();
let header = block.header();
let digest = block.hash();
Self {
digest,
height: header.height,
timestamp: header.timestamp,
difficulty: header.difficulty,
num_inputs: body.transaction.kernel.inputs.len(),
num_outputs: body.transaction.kernel.outputs.len(),
num_uncle_blocks: body.uncle_blocks.len(),
fee: body.transaction.kernel.fee,
mining_reward: crate::Block::get_mining_reward(header.height),
is_genesis: digest == genesis_digest,
is_tip: digest == tip_digest,
}
}
}

/// Provides alternatives for looking up a block.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum BlockSelector {
Digest(Digest), // Identifies block by Digest (hash)
Height(BlockHeight), // Identifies block by Height (count from genesis)
Genesis, // Indicates the genesis block
Tip, // Indicates the latest canonical block
}

impl BlockSelector {
async fn as_digest(&self, state: &GlobalState) -> Option<Digest> {
match self {
BlockSelector::Digest(d) => Some(*d),
BlockSelector::Height(h) => {
state
.chain
.archival_state()
.block_height_to_canonical_block_digest(*h, state.chain.light_state().hash())
.await
}
BlockSelector::Tip => Some(state.chain.light_state().hash()),
BlockSelector::Genesis => Some(state.chain.archival_state().genesis_block().hash()),
}
}
}

#[tarpc::service]
pub trait RPC {
/******** READ DATA ********/
Expand Down

0 comments on commit 3e21a69

Please sign in to comment.