Skip to content

Commit

Permalink
json: Use i64 where possible
Browse files Browse the repository at this point in the history
The Bitcoin Core JSONRPC API has fields marked as 'numeric'. It is not
obvious what Rust type these fields should be.

We want the version specific JSON types to just work (TM).

1. We use an `i64` because its the biggest signed integer on "common"
   machines.
2. We use a signed integer because Core sometimes returns -1.

Some fields are then converted to `rust-bitcoin` types that expect a
`u64` (eg sats), these are left as `u64` and we just hope Core never
returns -1 for any of them.
  • Loading branch information
tcharding committed Sep 12, 2024
1 parent d007f78 commit 23d38ff
Show file tree
Hide file tree
Showing 4 changed files with 418 additions and 177 deletions.
46 changes: 46 additions & 0 deletions json/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,49 @@ pub mod v27;

// JSON types that model _all_ `bitcoind` versions.
pub mod model;

use std::fmt;

/// Converts an `i64` numeric type to a `u32`.
///
/// The Bitcoin Core JSONRPC API has fields marked as 'numeric'. It is not obvious what Rust
/// type these fields should be.
///
/// We want the version specific JSON types to just work (TM).
///
/// 1. We use an `i64` because its the biggest signed integer on "common" machines.
/// 2. We use a signed integer because Core sometimes returns -1.
///
/// (2) was discovered in the wild but is hard to test for.
pub fn to_u32(value: i64, field: &str) -> Result<u32, NumericError> {
if value.is_negative() {
return Err(NumericError::Negative { value, field: field.to_owned() });
}
u32::try_from(value).map_err(|_| NumericError::Overflow { value, field: field.to_owned() })
}

/// Error converting an `i64` to a `u32`.
///
/// If we expect a numeric value to sanely fit inside a `u32` we use that type in the `model`
/// module, this requires converting the `i64` returned by the JSONRPC API into a `u32`, if our
/// expectations are not met this error will be encountered.
#[derive(Debug)]
pub enum NumericError {
/// Expected an unsigned numeric value however the value was negative.
Negative { field: String, value: i64 },
/// A value larger than `u32::MAX` was unexpectedly encountered.
Overflow { field: String, value: i64 },
}

impl fmt::Display for NumericError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use NumericError::*;

match *self {
Negative{ ref field, value } => write!(f, "expected an unsigned numeric value however the value was negative (field name: {} value: {})", field, value),
Overflow { ref field, value } => write!(f, "a value larger than `u32::MAX` was unexpectedly encountered (field name: {} Value: {})", field, value),
}
}
}

impl std::error::Error for NumericError {}
83 changes: 40 additions & 43 deletions json/src/model/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,25 @@ pub struct GetBlockVerbosityOne {
/// The block hash (same as provided) in RPC call.
pub hash: BlockHash,
/// The number of confirmations, or -1 if the block is not on the main chain.
pub confirmations: i32,
pub confirmations: i64,
/// The block size.
pub size: usize,
pub size: u32,
/// The block size excluding witness data.
pub stripped_size: Option<usize>, // Weight?
pub stripped_size: Option<u32>,
/// The block weight as defined in BIP-141.
pub weight: Weight,
/// The block height or index.
pub height: usize,
pub height: u32,
/// The block version.
pub version: block::Version,
/// The block version formatted in hexadecimal.
pub version_hex: String,
/// The merkle root.
pub merkle_root: String,
/// The transaction ids.
pub tx: Vec<Txid>,
/// The block time expressed in UNIX epoch time.
pub time: usize,
pub time: u32,
/// The median block time expressed in UNIX epoch time.
pub median_time: Option<usize>,
pub median_time: Option<u32>,
/// The nonce.
pub nonce: u32,
/// The bits.
Expand All @@ -71,15 +69,15 @@ pub struct GetBlockchainInfo {
/// Current network name as defined in BIP70 (main, test, signet, regtest).
pub chain: Network,
/// The current number of blocks processed in the server.
pub blocks: u64,
pub blocks: u32,
/// The current number of headers we have validated.
pub headers: u64,
pub headers: u32,
/// The hash of the currently best block.
pub best_block_hash: BlockHash,
/// The current difficulty.
pub difficulty: f64,
/// Median time for the current best block.
pub median_time: u64,
pub median_time: u32,
/// Estimate of verification progress (between 0 and 1).
pub verification_progress: f64,
/// Estimate of whether this node is in Initial Block Download (IBD) mode.
Expand All @@ -91,11 +89,11 @@ pub struct GetBlockchainInfo {
/// If the blocks are subject to pruning.
pub pruned: bool,
/// Lowest-height complete block stored (only present if pruning is enabled)
pub prune_height: Option<u64>,
pub prune_height: Option<u32>,
/// Whether automatic pruning is enabled (only present if pruning is enabled).
pub automatic_pruning: Option<bool>,
/// The target size used by pruning (only present if automatic pruning is enabled).
pub prune_target_size: Option<u64>,
pub prune_target_size: Option<u32>,
/// Status of softforks in progress, maps softfork name -> [`Softfork`].
pub softforks: BTreeMap<String, Softfork>,
/// Any network and blockchain warnings.
Expand All @@ -111,7 +109,7 @@ pub struct Softfork {
/// The status of bip9 softforks (only for "bip9" type).
pub bip9: Option<Bip9SoftforkInfo>,
/// Height of the first block which the rules are or will be enforced (only for "buried" type, or "bip9" type with "active" status).
pub height: Option<u64>,
pub height: Option<u32>,
/// `true` if the rules are enforced for the mempool and the next block.
pub active: bool,
}
Expand All @@ -138,9 +136,9 @@ pub struct Bip9SoftforkInfo {
/// The bit (0-28) in the block version field used to signal this softfork (only for "started" status).
pub bit: Option<u8>,
/// The minimum median time past of a block at which the bit gains its meaning.
pub start_time: i64,
pub start_time: u32,
/// The median time past of a block at which the deployment is considered failed if not yet locked in.
pub timeout: u64,
pub timeout: u32,
/// Height of the first block to which the status applies.
pub since: u32,
/// Numeric statistics about BIP-9 signalling for a softfork (only for "started" status).
Expand Down Expand Up @@ -197,17 +195,17 @@ pub struct GetBlockHeaderVerbose {
/// The number of confirmations, or -1 if the block is not on the main chain.
pub confirmations: i64,
/// The block height or index.
pub height: u64,
pub height: u32,
/// Block version, now repurposed for soft fork signalling.
pub version: block::Version,
/// The root hash of the Merkle tree of transactions in the block.
pub merkle_root: TxMerkleNode,
/// The timestamp of the block, as claimed by the miner (seconds since epoch (Jan 1 1970 GMT).
pub time: u64,
pub time: u32,
/// The median block time in seconds since epoch (Jan 1 1970 GMT).
pub median_time: u64,
pub median_time: u32,
/// The nonce.
pub nonce: u64,
pub nonce: u32,
/// The target value below which the blockhash must lie.
pub bits: CompactTarget,
/// The difficulty.
Expand All @@ -223,66 +221,65 @@ pub struct GetBlockHeaderVerbose {
}

/// Models the result of JSON-RPC method `getblockstats`.
// FIXME: Should all the sizes be u32, u64, or usize?
pub struct GetBlockStats {
/// Average fee in the block.
pub average_fee: Amount,
/// Average feerate.
pub average_fee_rate: Option<FeeRate>,
/// Average transaction size.
pub average_tx_size: u64,
pub average_tx_size: u32,
/// The block hash (to check for potential reorgs).
pub block_hash: BlockHash,
/// Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte).
pub fee_rate_percentiles: Vec<Option<FeeRate>>,
/// The height of the block.
pub height: u64,
pub height: u32,
/// The number of inputs (excluding coinbase).
pub inputs: u64,
pub inputs: u32,
/// Maximum fee in the block.
pub max_fee: Amount,
/// Maximum feerate (in satoshis per virtual byte).
pub max_fee_rate: Option<FeeRate>,
/// Maximum transaction size.
pub max_tx_size: u64,
pub max_tx_size: u32,
/// Truncated median fee in the block.
pub median_fee: Amount,
/// The block median time past.
pub median_time: u32,
/// Truncated median transaction size
pub median_tx_size: u64,
pub median_tx_size: u32,
/// Minimum fee in the block.
pub minimum_fee: Amount,
/// Minimum feerate (in satoshis per virtual byte).
pub minimum_fee_rate: Option<FeeRate>,
/// Minimum transaction size.
pub minimum_tx_size: u64,
pub minimum_tx_size: u32,
/// The number of outputs.
pub outputs: u64,
pub outputs: u32,
/// The block subsidy.
pub subsidy: Amount,
/// Total size of all segwit transactions.
pub segwit_total_size: u64,
pub segwit_total_size: u32,
/// Total weight of all segwit transactions divided by segwit scale factor (4).
pub segwit_total_weight: Option<Weight>,
/// The number of segwit transactions.
pub segwit_txs: u64,
pub segwit_txs: u32,
/// The block time.
pub time: u32,
/// Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee]).
pub total_out: Amount,
/// Total size of all non-coinbase transactions.
pub total_size: u64,
pub total_size: u32,
/// Total weight of all non-coinbase transactions divided by segwit scale factor (4).
pub total_weight: Option<Weight>,
/// The fee total.
pub total_fee: Amount,
/// The number of transactions (excluding coinbase).
pub txs: u64,
pub txs: u32,
/// The increase/decrease in the number of unspent outputs.
pub utxo_increase: u64,
pub utxo_increase: i32,
/// The increase/decrease in size for the utxo index (not discounting op_return and similar).
pub utxo_size_increase: u64,
pub utxo_size_increase: i32,
}

/// Result of JSON-RPC method `getchaintips`.
Expand All @@ -293,11 +290,11 @@ pub struct GetChainTips(pub Vec<ChainTips>);
#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
pub struct ChainTips {
/// Height of the chain tip.
pub height: u64,
pub height: u32,
/// Block hash of the tip.
pub hash: BlockHash,
/// Zero for main chain.
pub branch_length: u64,
pub branch_length: u32,
/// "active" for the main chain.
pub status: ChainTipsStatus,
}
Expand All @@ -324,17 +321,17 @@ pub struct GetChainTxStats {
/// The timestamp for the final block in the window in UNIX format.
pub time: u32,
/// The total number of transactions in the chain up to that point.
pub tx_count: u64,
pub tx_count: u32,
/// The hash of the final block in the window.
pub window_final_block_hash: BlockHash,
/// Size of the window in number of blocks.
pub window_block_count: u64,
pub window_block_count: u32,
/// The number of transactions in the window. Only returned if "window_block_count" is > 0.
pub window_tx_count: Option<u64>,
pub window_tx_count: Option<u32>,
/// The elapsed time in the window in seconds. Only returned if "window_block_count" is > 0.
pub window_interval: Option<u64>,
pub window_interval: Option<u32>,
/// The average rate of transactions per second in the window. Only returned if "window_interval" is > 0.
pub tx_rate: Option<u64>,
pub tx_rate: Option<u32>,
}

/// Result of JSON-RPC method `getdifficulty`.
Expand All @@ -354,8 +351,8 @@ pub struct GetMempoolAncestorsVerbose {}
pub struct GetTxOut {
/// The hash of the block at the tip of the chain.
pub best_block: BlockHash,
/// The number of confirmations.
pub confirmations: u32,
/// The number of confirmations (signed to match other types with the same field name).
pub confirmations: i64,
/// The returned `TxOut` (strongly typed).
pub tx_out: TxOut,
/// Address that `tx_out` spends to.
Expand Down
Loading

0 comments on commit 23d38ff

Please sign in to comment.