Skip to content
Merged
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
17 changes: 13 additions & 4 deletions bin/floresta-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ fn do_request(cmd: &Cli, client: Client) -> anyhow::Result<String> {
Methods::SendRawTransaction { tx } => {
serde_json::to_string_pretty(&client.send_raw_transaction(tx)?)?
}
Methods::GetBlockHeader { hash } => {
serde_json::to_string_pretty(&client.get_block_header(hash)?)?
Methods::GetBlockHeader { hash, verbosity } => {
serde_json::to_string_pretty(&client.get_block_header(hash, verbosity)?)?
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

65acc75 and 481e59c
can be squashed, they pretty much jut add verbosity support for getblockheader

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
Methods::LoadDescriptor { desc } => {
serde_json::to_string_pretty(&client.load_descriptor(desc)?)?
Expand Down Expand Up @@ -262,8 +262,17 @@ pub enum Methods {
SendRawTransaction { tx: String },

/// Returns the block header for the given block hash
#[command(name = "getblockheader")]
GetBlockHeader { hash: BlockHash },
#[doc = include_str!("../../../doc/rpc/getblockheader.md")]
#[command(
name = "getblockheader",
about = "Returns the block header for the given block hash",
long_about = Some(include_str!("../../../doc/rpc/getblockheader.md")),
disable_help_subcommand = true
)]
GetBlockHeader {
hash: BlockHash,
verbosity: Option<bool>,
},

/// Loads a new descriptor to the watch only wallet
#[doc = include_str!("../../../doc/rpc/loaddescriptor.md")]
Expand Down
128 changes: 88 additions & 40 deletions crates/floresta-node/src/json_rpc/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use bitcoin::ScriptBuf;
use bitcoin::Txid;
use bitcoin::VarInt;
use corepc_types::v29::GetTxOut;
use corepc_types::v30::GetBlockHeaderVerbose;
use corepc_types::v30::GetBlockVerboseOne;
use corepc_types::ScriptPubkey;
use floresta_chain::extensions::HeaderExt;
Expand All @@ -24,6 +25,7 @@ use serde_json::json;
use serde_json::Value;
use tracing::debug;

use super::res::GetBlockHeaderRes;
use super::res::GetBlockchainInfoRes;
use super::res::GetTxOutProof;
use super::res::JsonRpcError;
Expand Down Expand Up @@ -106,7 +108,7 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
at: u32,
) -> Result<u32, JsonRpcError> {
let hash = provider.get_block_hash(at)?;
let block = provider.get_block_header(hash)?;
let block = provider.get_block_header_inner(hash)?;
Ok(block.time)
}

Expand Down Expand Up @@ -182,31 +184,15 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
return Ok(GetBlockRes::Zero(hex));
}
if verbosity == 1 {
let header = &block.header;
let height = header.get_height(&self.chain)?;
let median_time = header.calculate_median_time_past(&self.chain)?;
let chain_work = header.calculate_chain_work(&self.chain)?.to_string_hex();
let confirmations = header.get_confirmations(&self.chain)? as i64;
let version_hex = header.get_version_hex();

let next_block_hash = header
.get_next_block_hash(&self.chain)?
.map(|h| h.to_string());

let bits = header.get_bits_hex();
let difficulty = header.get_difficulty();
let target = header.get_target_hex();
let header_fields = self.get_block_header_verbose_inner(&block)?;

// Stripped size is the size of the block without witness data
// Header + VarInt for number of transactions + sum of base sizes of each transaction
let tx_count_varint_size = VarInt::from(block.txdata.len()).size();
let total_tx_base_size: usize = block.txdata.iter().map(|tx| tx.base_size()).sum();
let stripped_size_bytes = Header::SIZE + tx_count_varint_size + total_tx_base_size;

let stripped_size = Some(stripped_size_bytes as i64);

let previous_block_hash = (header.prev_blockhash != BlockHash::all_zeros())
.then_some(header.prev_blockhash.to_string());
let stripped_size = Some(stripped_size_bytes.try_into()?);

let tx = block
.txdata
Expand All @@ -215,26 +201,26 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
.collect();

let block = GetBlockVerboseOne {
bits,
chain_work,
confirmations,
difficulty,
hash: header.block_hash().to_string(),
height: height as i64,
merkle_root: header.merkle_root.to_string(),
nonce: header.nonce as i64,
previous_block_hash,
size: block.total_size() as i64,
time: header.time as i64,
bits: header_fields.bits,
chain_work: header_fields.chain_work,
confirmations: header_fields.confirmations,
difficulty: header_fields.difficulty,
hash: header_fields.hash,
height: header_fields.height,
merkle_root: header_fields.merkle_root,
nonce: header_fields.nonce,
previous_block_hash: header_fields.previous_block_hash,
size: block.total_size().try_into()?,
time: header_fields.time,
tx,
version: header.version.to_consensus(),
version_hex,
version: header_fields.version,
version_hex: header_fields.version_hex,
weight: block.weight().to_wu(),
median_time: Some(median_time as i64),
n_tx: block.txdata.len() as i64,
next_block_hash,
median_time: Some(header_fields.median_time),
n_tx: header_fields.n_tx.into(),
next_block_hash: header_fields.next_block_hash,
stripped_size,
target,
target: header_fields.target,
};

return Ok(GetBlockRes::One(Box::new(block)));
Expand Down Expand Up @@ -302,10 +288,23 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
}

// getblockheader
pub(super) fn get_block_header(&self, hash: BlockHash) -> Result<Header, JsonRpcError> {
self.chain
.get_block_header(&hash)
.map_err(|_| JsonRpcError::BlockNotFound)
pub(super) async fn get_block_header(
&self,
hash: BlockHash,
verbosity: bool,
) -> Result<GetBlockHeaderRes, JsonRpcError> {
let header = self.get_block_header_inner(hash)?;

if !verbosity {
let hex = serialize_hex(&header);
return Ok(GetBlockHeaderRes::Raw(hex));
}

let block = self.get_block_inner(hash).await?;

let get_block_header = self.get_block_header_verbose_inner(&block)?;

Ok(GetBlockHeaderRes::Verbose(Box::new(get_block_header)))
}

// getblockstats
Expand All @@ -320,6 +319,55 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
// getmempoolinfo
// getrawmempool

/// Same as `get_block_header_inner` but verbose.
fn get_block_header_verbose_inner(
&self,
block: &Block,
) -> Result<GetBlockHeaderVerbose, JsonRpcError> {
let header = &block.header;
let height = header.get_height(&self.chain)?;
let median_time = header.calculate_median_time_past(&self.chain)?;
let chain_work = header.calculate_chain_work(&self.chain)?.to_string_hex();
let confirmations = header.get_confirmations(&self.chain)?;
let version_hex = header.get_version_hex();

let next_block_hash = header
.get_next_block_hash(&self.chain)?
.map(|h| h.to_string());

let bits = header.get_bits_hex();
let difficulty = header.get_difficulty();
let target = header.get_target_hex();
Comment on lines +328 to +340
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extensions are shining

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, getblock and getblockheader differ in that one includes the transactions and the other doesn’t, basically. The rest of the fields would be the same.

let previous_block_hash = (header.prev_blockhash != BlockHash::all_zeros())
.then_some(header.prev_blockhash.to_string());

Ok(GetBlockHeaderVerbose {
bits,
chain_work,
confirmations: confirmations.into(),
difficulty,
hash: header.block_hash().to_string(),
height: height.into(),
median_time: median_time.into(),
next_block_hash,
version: header.version.to_consensus(),
version_hex,
previous_block_hash,
merkle_root: header.merkle_root.to_string(),
time: header.time.into(),
target,
nonce: header.nonce.into(),
n_tx: block.txdata.len().try_into()?,
})
}

/// Helper method to get a block header by its hash, used by multiple rpcs.
fn get_block_header_inner(&self, hash: BlockHash) -> Result<Header, JsonRpcError> {
self.chain
.get_block_header(&hash)
.map_err(|_| JsonRpcError::BlockNotFound)
}

/// Check if the script is anchor type
fn is_anchor_type(script: &Script) -> bool {
script.as_bytes().starts_with(&[0x51, 0x02, 0x4e, 0x73])
Expand Down
31 changes: 31 additions & 0 deletions crates/floresta-node/src/json_rpc/res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use core::fmt;
use core::fmt::Debug;
use core::fmt::Display;
use core::fmt::Formatter;
use core::num::TryFromIntError;
use std::convert::Infallible;

use axum::response::IntoResponse;
use corepc_types::v30::GetBlockHeaderVerbose;
use corepc_types::v30::GetBlockVerboseOne;
use floresta_chain::extensions::HeaderExtError;
use floresta_common::impl_error_from;
Expand Down Expand Up @@ -131,6 +134,18 @@ pub enum GetBlockRes {
One(Box<GetBlockVerboseOne>),
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
/// The response for `getblockheader`, which can be either a raw hex-encoded block header or a verbose
/// one with all the fields parsed and decoded.
pub enum GetBlockHeaderRes {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstrings for this one ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// The raw hex-encoded block header, as returned by `getblockheader` with verbosity false
Raw(String),

/// A verbose block header, as returned by `getblockheader` with verbosity true
Verbose(Box<GetBlockHeaderVerbose>),
}
Comment on lines +143 to +147
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Raw(String),
/// A verbose block header, as returned by `getblockheader` with verbosity true
Verbose(Box<GetBlockHeaderVerbose>),
}
Raw(String),
/// A verbose block header, as returned by `getblockheader` with verbosity true
Verbose(Box<GetBlockHeaderVerbose>),
}

nit

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a pending review with this, forgot to publish.

Agree 👍

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


#[derive(Debug, Deserialize, Serialize)]
pub struct RpcError {
pub code: i32,
Expand Down Expand Up @@ -226,6 +241,9 @@ pub enum JsonRpcError {

/// Something went wrong when attempting to publish a transaction to mempool
MempoolAccept(MempoolError),

/// A numeric conversion overflows, e.g., u64 to u32
ConversionOverflow(String),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, it feels really off to have this kind of error here, surely theres an existing alternative for this

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think there are alternatives, because there are some situations where we’re working with usize, whose size varies. So sometimes we need to convert usize to u32 or i64, and there’s a risk of overflow depending on the computer’s architecture.

}

impl_error_from!(JsonRpcError, MempoolError, MempoolAccept);
Expand Down Expand Up @@ -260,6 +278,7 @@ impl Display for JsonRpcError {
JsonRpcError::InvalidDisconnectNodeCommand => write!(f, "Invalid disconnectnode command"),
JsonRpcError::PeerNotFound => write!(f, "Peer not found in the peer list"),
JsonRpcError::MempoolAccept(e) => write!(f, "Could not send transaction to mempool due to {e}"),
JsonRpcError::ConversionOverflow(e) => write!(f, "Numeric conversion overflow: {e}"),
}
}
}
Expand Down Expand Up @@ -289,6 +308,18 @@ impl From<HeaderExtError> for JsonRpcError {
}
}

impl From<TryFromIntError> for JsonRpcError {
fn from(e: TryFromIntError) -> Self {
JsonRpcError::ConversionOverflow(e.to_string())
}
}

impl From<Infallible> for JsonRpcError {
fn from(e: Infallible) -> Self {
JsonRpcError::ConversionOverflow(e.to_string())
}
}

impl_error_from!(JsonRpcError, DescriptorError, InvalidDescriptor);

impl<T: Debug> From<floresta_watch_only::WatchOnlyError<T>> for JsonRpcError {
Expand Down
7 changes: 6 additions & 1 deletion crates/floresta-node/src/json_rpc/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,11 @@ async fn handle_json_rpc_request(

"getblockheader" => {
let hash = get_hash(&params, 0, "block_hash")?;
let verbosity = get_optional_field(&params, 1, "verbosity", get_bool)?.unwrap_or(true);

state
.get_block_header(hash)
.get_block_header(hash, verbosity)
.await
.map(|h| serde_json::to_value(h).unwrap())
}

Expand Down Expand Up @@ -440,6 +443,7 @@ fn get_http_error_code(err: &JsonRpcError) -> u16 {
| JsonRpcError::InvalidParameterType(_)
| JsonRpcError::MissingParameter(_)
| JsonRpcError::ChainWorkOverflow
| JsonRpcError::ConversionOverflow(_)
| JsonRpcError::MempoolAccept(_)
| JsonRpcError::Wallet(_) => 400,

Expand Down Expand Up @@ -480,6 +484,7 @@ fn get_json_rpc_error_code(err: &JsonRpcError) -> i32 {
| JsonRpcError::InvalidRescanVal
| JsonRpcError::NoAddressesToRescan
| JsonRpcError::ChainWorkOverflow
| JsonRpcError::ConversionOverflow(_)
| JsonRpcError::Wallet(_)
| JsonRpcError::MempoolAccept(_) => -32600,

Expand Down
10 changes: 8 additions & 2 deletions crates/floresta-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ mod tests {

use crate::jsonrpc_client::Client;
use crate::rpc::FlorestaRPC;
use crate::rpc_types::GetBlockHeaderRes;
use crate::rpc_types::GetBlockRes;

struct Florestad {
Expand Down Expand Up @@ -226,9 +227,14 @@ mod tests {
let (_proc, client) = start_florestad();

let blockhash = client.get_block_hash(0).expect("rpc not working");
let block_header = client.get_block_header(blockhash).expect("rpc not working");
let block_header = client
.get_block_header(blockhash, Some(true))
.expect("rpc not working");
let GetBlockHeaderRes::Verbose(block_header) = block_header else {
panic!("Expected verbose block header");
};

assert_eq!(block_header.block_hash(), blockhash);
assert_eq!(block_header.hash, blockhash.to_string());
}

#[test]
Expand Down
21 changes: 17 additions & 4 deletions crates/floresta-rpc/src/rpc.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT OR Apache-2.0

use core::fmt::Debug;
use std::vec;

use bitcoin::block::Header as BlockHeader;
use bitcoin::BlockHash;
use bitcoin::Txid;
use corepc_types::v29::GetTxOut;
Expand Down Expand Up @@ -42,7 +42,12 @@ pub trait FlorestaRPC {
/// in the Bitcoin protocol specification. A header contains the block's version,
/// the previous block hash, the merkle root, the timestamp, the difficulty target,
/// and the nonce.
fn get_block_header(&self, hash: BlockHash) -> Result<BlockHeader>;
#[doc = include_str!("../../../doc/rpc/getblockheader.md")]
fn get_block_header(
&self,
hash: BlockHash,
verbosity: Option<bool>,
) -> Result<GetBlockHeaderRes>;
/// Gets a transaction from the blockchain
///
/// This method returns a transaction that's cached in our wallet. If the verbosity flag is
Expand Down Expand Up @@ -323,8 +328,16 @@ impl<T: JsonRPCClient> FlorestaRPC for T {
self.call("getblockfilter", &[Value::Number(Number::from(height))])
}

fn get_block_header(&self, hash: BlockHash) -> Result<BlockHeader> {
self.call("getblockheader", &[Value::String(hash.to_string())])
fn get_block_header(
&self,
hash: BlockHash,
verbosity: Option<bool>,
) -> Result<GetBlockHeaderRes> {
let mut params = vec![Value::String(hash.to_string())];
if let Some(verbosity) = verbosity {
params.push(Value::Bool(verbosity));
}
self.call("getblockheader", &params)
}

fn get_blockchain_info(&self) -> Result<GetBlockchainInfoRes> {
Expand Down
Loading
Loading