diff --git a/json/src/lib.rs b/json/src/lib.rs index 5e1c758..83139dd 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -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 { + 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 {} diff --git a/json/src/model/blockchain.rs b/json/src/model/blockchain.rs index 135b684..a49b545 100644 --- a/json/src/model/blockchain.rs +++ b/json/src/model/blockchain.rs @@ -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, // Weight? + pub stripped_size: Option, /// 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, /// 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, + pub median_time: Option, /// The nonce. pub nonce: u32, /// The bits. @@ -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. @@ -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, + pub prune_height: Option, /// Whether automatic pruning is enabled (only present if pruning is enabled). pub automatic_pruning: Option, /// The target size used by pruning (only present if automatic pruning is enabled). - pub prune_target_size: Option, + pub prune_target_size: Option, /// Status of softforks in progress, maps softfork name -> [`Softfork`]. pub softforks: BTreeMap, /// Any network and blockchain warnings. @@ -111,7 +109,7 @@ pub struct Softfork { /// The status of bip9 softforks (only for "bip9" type). pub bip9: Option, /// 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, + pub height: Option, /// `true` if the rules are enforced for the mempool and the next block. pub active: bool, } @@ -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, /// 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). @@ -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. @@ -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, /// 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>, /// 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, /// 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, /// 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, /// 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, /// 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`. @@ -293,11 +290,11 @@ pub struct GetChainTips(pub Vec); #[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, } @@ -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, + pub window_tx_count: Option, /// The elapsed time in the window in seconds. Only returned if "window_block_count" is > 0. - pub window_interval: Option, + pub window_interval: Option, /// The average rate of transactions per second in the window. Only returned if "window_interval" is > 0. - pub tx_rate: Option, + pub tx_rate: Option, } /// Result of JSON-RPC method `getdifficulty`. @@ -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. diff --git a/json/src/v17/blockchain.rs b/json/src/v17/blockchain.rs index fa0a657..4896083 100644 --- a/json/src/v17/blockchain.rs +++ b/json/src/v17/blockchain.rs @@ -18,7 +18,7 @@ use bitcoin::{ use internals::write_err; use serde::{Deserialize, Serialize}; -use crate::model; +use crate::{model, NumericError}; /// Result of JSON-RPC method `getbestblockhash`. /// @@ -64,16 +64,16 @@ pub struct GetBlockVerbosityOne { /// The block hash (same as provided) in RPC call. pub hash: String, /// 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: i64, /// The block size excluding witness data. #[serde(rename = "strippedsize")] - pub stripped_size: Option, + pub stripped_size: Option, /// The block weight as defined in BIP-141. pub weight: u64, /// The block height or index. - pub height: usize, + pub height: i64, /// The block version. pub version: i32, /// The block version formatted in hexadecimal. @@ -82,15 +82,15 @@ pub struct GetBlockVerbosityOne { /// The merkle root #[serde(rename = "merkleroot")] pub merkle_root: String, - /// The transaction ids + /// The transaction ids. pub tx: Vec, /// The block time expressed in UNIX epoch time. - pub time: usize, + pub time: i64, /// The median block time expressed in UNIX epoch time. #[serde(rename = "mediantime")] - pub median_time: Option, - /// The nonce - pub nonce: u32, + pub median_time: Option, + /// The nonce (this should be only 4 bytes). + pub nonce: i64, /// The bits. pub bits: String, /// The difficulty. @@ -100,7 +100,7 @@ pub struct GetBlockVerbosityOne { pub chain_work: String, /// The number of transactions in the block. #[serde(rename = "nTx")] - pub n_tx: u32, + pub n_tx: i64, /// The hash of the previous block (if available). #[serde(rename = "previousblockhash")] pub previous_block_hash: Option, @@ -115,45 +115,46 @@ impl GetBlockVerbosityOne { use GetBlockVerbosityOneError as E; let hash = self.hash.parse::().map_err(E::Hash)?; - let weight = Weight::from_wu(self.weight); // TODO: Confirm this uses weight units. + let stripped_size = + self.stripped_size.map(|size| crate::to_u32(size, "stripped_size")).transpose()?; + let weight = Weight::from_wu(self.weight); // FIXME: Confirm this uses weight units. let version = block::Version::from_consensus(self.version); - let tx = self .tx .iter() .map(|t| encode::deserialize_hex::(t).map_err(E::Tx)) .collect::, _>>()?; - + let median_time = self.median_time.map(|t| crate::to_u32(t, "median_time")).transpose()?; let bits = CompactTarget::from_unprefixed_hex(&self.bits).map_err(E::Bits)?; let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; - - let previous_block_hash = match self.previous_block_hash { - Some(hash) => Some(hash.parse::().map_err(E::PreviousBlockHash)?), - None => None, - }; - let next_block_hash = match self.next_block_hash { - Some(hash) => Some(hash.parse::().map_err(E::NextBlockHash)?), - None => None, - }; + let previous_block_hash = self + .previous_block_hash + .map(|s| s.parse::()) + .transpose() + .map_err(E::PreviousBlockHash)?; + let next_block_hash = self + .next_block_hash + .map(|s| s.parse::()) + .transpose() + .map_err(E::NextBlockHash)?; Ok(model::GetBlockVerbosityOne { hash, confirmations: self.confirmations, - size: self.size, - stripped_size: self.stripped_size, + size: crate::to_u32(self.size, "size")?, + stripped_size, weight, - height: self.height, + height: crate::to_u32(self.height, "height")?, version, - version_hex: self.version_hex, - merkle_root: self.merkle_root, // TODO: Use hash, which one depends on segwit or not + merkle_root: self.merkle_root, // TODO: Use hash, which one depends on segwit or not. tx, - time: self.time, // TODO: Use stronger type. - median_time: self.median_time, - nonce: self.nonce, + time: crate::to_u32(self.time, "time")?, + median_time, + nonce: crate::to_u32(self.nonce, "nonce")?, bits, difficulty: self.difficulty, chain_work, - n_tx: self.n_tx, + n_tx: crate::to_u32(self.n_tx, "n_tx")?, previous_block_hash, next_block_hash, }) @@ -163,6 +164,8 @@ impl GetBlockVerbosityOne { /// Error when converting a `GetBlockVerbosityOne` type into the model type. #[derive(Debug)] pub enum GetBlockVerbosityOneError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), /// Conversion of the transaction `hash` field failed. Hash(hex::HexToArrayError), /// Conversion of the transaction `hex` field failed. @@ -182,6 +185,7 @@ impl fmt::Display for GetBlockVerbosityOneError { use GetBlockVerbosityOneError::*; match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), Hash(ref e) => write_err!(f, "conversion of the `hash` field failed"; e), Tx(ref e) => write_err!(f, "conversion of the `tx` field failed"; e), Bits(ref e) => write_err!(f, "conversion of the `bits` field failed"; e), @@ -199,6 +203,7 @@ impl std::error::Error for GetBlockVerbosityOneError { use GetBlockVerbosityOneError::*; match *self { + Numeric(ref e) => Some(e), Hash(ref e) => Some(e), Tx(ref e) => Some(e), Bits(ref e) => Some(e), @@ -209,6 +214,10 @@ impl std::error::Error for GetBlockVerbosityOneError { } } +impl From for GetBlockVerbosityOneError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getblockchaininfo`. /// /// Method call: `getblockchaininfo` @@ -219,9 +228,9 @@ pub struct GetBlockchainInfo { /// Current network name as defined in BIP70 (main, test, signet, regtest). pub chain: String, /// The current number of blocks processed in the server. - pub blocks: u64, + pub blocks: i64, /// The current number of headers we have validated. - pub headers: u64, + pub headers: i64, /// The hash of the currently best block. #[serde(rename = "bestblockhash")] pub best_block_hash: String, @@ -229,7 +238,7 @@ pub struct GetBlockchainInfo { pub difficulty: f64, /// Median time for the current best block. #[serde(rename = "mediantime")] - pub median_time: u64, + pub median_time: i64, /// Estimate of verification progress (between 0 and 1). #[serde(rename = "verificationprogress")] pub verification_progress: f64, @@ -245,11 +254,11 @@ pub struct GetBlockchainInfo { pub pruned: bool, /// Lowest-height complete block stored (only present if pruning is enabled). #[serde(rename = "pruneheight")] - pub prune_height: Option, + pub prune_height: Option, /// Whether automatic pruning is enabled (only present if pruning is enabled). pub automatic_pruning: Option, /// The target size used by pruning (only present if automatic pruning is enabled). - pub prune_target_size: Option, + pub prune_target_size: Option, /// Status of softforks in progress. pub softforks: Vec, /// Status of BIP-9 softforks in progress, maps softfork name -> [`Softfork`]. @@ -264,7 +273,7 @@ pub struct Softfork { /// Name of softfork. id: String, /// Block version. - version: usize, + version: i64, /// Progress toward rejecting pre-softfork blocks. reject: SoftforkReject, } @@ -287,9 +296,9 @@ pub struct Bip9Softfork { #[serde(rename = "startTime")] pub start_time: i64, /// The median time past of a block at which the deployment is considered failed if not yet locked in. - pub timeout: u64, + pub timeout: i64, /// Height of the first block to which the status applies. - pub since: u32, + pub since: i64, } /// BIP-9 softfork status: one of "defined", "started", "locked_in", "active", "failed". @@ -317,24 +326,27 @@ impl GetBlockchainInfo { let best_block_hash = self.best_block_hash.parse::().map_err(E::BestBlockHash)?; let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; - + let prune_height = + self.prune_height.map(|h| crate::to_u32(h, "prune_height")).transpose()?; + let prune_target_size = + self.prune_target_size.map(|h| crate::to_u32(h, "prune_target_size")).transpose()?; let softforks = BTreeMap::new(); // TODO: Handle softforks stuff. Ok(model::GetBlockchainInfo { chain, - blocks: self.blocks, - headers: self.headers, + blocks: crate::to_u32(self.blocks, "blocks")?, + headers: crate::to_u32(self.headers, "headers")?, best_block_hash, difficulty: self.difficulty, - median_time: self.median_time, + median_time: crate::to_u32(self.median_time, "median_time")?, verification_progress: self.verification_progress, initial_block_download: self.initial_block_download, chain_work, size_on_disk: self.size_on_disk, pruned: self.pruned, - prune_height: self.prune_height, + prune_height, automatic_pruning: self.automatic_pruning, - prune_target_size: self.prune_target_size, + prune_target_size, softforks, warnings: self.warnings, }) @@ -359,8 +371,13 @@ impl Bip9SoftforkStatus { /// Error when converting a `GetBlockchainInfo` type into the model type. #[derive(Debug)] pub enum GetBlockchainInfoError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `chain` field failed. Chain(network::ParseNetworkError), + /// Conversion of the `best_block_hash` field failed. BestBlockHash(hex::HexToArrayError), + /// Conversion of the `chain_work` field failed. ChainWork(UnprefixedHexError), } @@ -369,6 +386,7 @@ impl fmt::Display for GetBlockchainInfoError { use GetBlockchainInfoError::*; match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), Chain(ref e) => write_err!(f, "conversion of the `chain` field failed"; e), BestBlockHash(ref e) => write_err!(f, "conversion of the `best_block_hash` field failed"; e), @@ -382,6 +400,7 @@ impl std::error::Error for GetBlockchainInfoError { use GetBlockchainInfoError::*; match *self { + Numeric(ref e) => Some(e), Chain(ref e) => Some(e), BestBlockHash(ref e) => Some(e), ChainWork(ref e) => Some(e), @@ -389,6 +408,10 @@ impl std::error::Error for GetBlockchainInfoError { } } +impl From for GetBlockchainInfoError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getblockcount`. /// /// > getblockcount @@ -496,7 +519,7 @@ 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: i64, /// The block version. pub version: i32, /// The block version formatted in hexadecimal. @@ -506,12 +529,12 @@ pub struct GetBlockHeaderVerbose { #[serde(rename = "merkleroot")] pub merkle_root: String, /// The block time in seconds since epoch (Jan 1 1970 GMT). - pub time: u64, + pub time: i64, /// The median block time in seconds since epoch (Jan 1 1970 GMT). #[serde(rename = "mediantime")] - pub median_time: u64, + pub median_time: i64, /// The nonce. - pub nonce: u64, + pub nonce: i64, /// The bits. pub bits: String, /// The difficulty. @@ -552,12 +575,12 @@ impl GetBlockHeaderVerbose { Ok(model::GetBlockHeaderVerbose { hash, confirmations: self.confirmations, - height: self.height, + height: crate::to_u32(self.height, "height")?, version, merkle_root, - time: self.time, - median_time: self.median_time, - nonce: self.nonce, + time: crate::to_u32(self.time, "time")?, + median_time: crate::to_u32(self.median_time, "median_time")?, + nonce: crate::to_u32(self.nonce, "nonce")?, bits, difficulty: self.difficulty, chain_work, @@ -574,6 +597,8 @@ impl GetBlockHeaderVerbose { /// Error when converting a `GetBlockHeader` type into the model type. #[derive(Debug)] pub enum GetBlockHeaderVerboseError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), /// Conversion of `hash` field failed. Hash(hex::HexToArrayError), /// Conversion of `merkle_root` field failed. @@ -588,6 +613,44 @@ pub enum GetBlockHeaderVerboseError { NextBlockHash(hex::HexToArrayError), } +impl fmt::Display for GetBlockHeaderVerboseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetBlockHeaderVerboseError::*; + + match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), + Hash(ref e) => write_err!(f, "conversion of the `hash` field failed"; e), + MerkleRoot(ref e) => write_err!(f, "conversion of the `merkle_root` field failed"; e), + Bits(ref e) => write_err!(f, "conversion of the `bit` field failed"; e), + ChainWork(ref e) => write_err!(f, "conversion of the `chain_work` field failed"; e), + PreviousBlockHash(ref e) => + write_err!(f, "conversion of the `previous_bock_hash` field failed"; e), + NextBlockHash(ref e) => + write_err!(f, "conversion of the `next_bock_hash` field failed"; e), + } + } +} + +impl std::error::Error for GetBlockHeaderVerboseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetBlockHeaderVerboseError::*; + + match *self { + Numeric(ref e) => Some(e), + Hash(ref e) => Some(e), + MerkleRoot(ref e) => Some(e), + Bits(ref e) => Some(e), + ChainWork(ref e) => Some(e), + PreviousBlockHash(ref e) => Some(e), + NextBlockHash(ref e) => Some(e), + } + } +} + +impl From for GetBlockHeaderVerboseError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getblockstats`. /// /// > getblockstats hash_or_height ( stats ) @@ -608,18 +671,17 @@ pub enum GetBlockHeaderVerboseError { /// > ,... /// > ] #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -// FIXME: Should these fields be u64 or u32? pub struct GetBlockStats { /// Average fee in the block. #[serde(rename = "avgfee")] pub average_fee: u64, - // TODO: Remember these doces will become silently stale when unit changes in a later version of Core. + // FIXME: Remember these docs will become silently stale when unit changes in a later version of Core. /// Average feerate (in satoshis per virtual byte). #[serde(rename = "avgfeerate")] pub average_fee_rate: u64, /// Average transaction size. #[serde(rename = "avgtxsize")] - pub average_tx_size: u64, + pub average_tx_size: i64, /// The block hash (to check for potential reorgs). #[serde(rename = "blockhash")] pub block_hash: String, @@ -628,10 +690,10 @@ pub struct GetBlockStats { #[serde(rename = "feerate_percentiles")] pub fee_rate_percentiles: [u64; 5], /// The height of the block. - pub height: u64, + pub height: i64, /// The number of inputs (excluding coinbase). #[serde(rename = "ins")] - pub inputs: u64, + pub inputs: i64, /// Maximum fee in the block. #[serde(rename = "maxfee")] pub max_fee: u64, @@ -640,16 +702,16 @@ pub struct GetBlockStats { pub max_fee_rate: u64, /// Maximum transaction size. #[serde(rename = "maxtxsize")] - pub max_tx_size: u64, + pub max_tx_size: i64, /// Truncated median fee in the block. #[serde(rename = "medianfee")] pub median_fee: u64, /// The block median time past. #[serde(rename = "mediantime")] - pub median_time: u32, + pub median_time: i64, /// Truncated median transaction size #[serde(rename = "mediantxsize")] - pub median_tx_size: u64, + pub median_tx_size: i64, /// Minimum fee in the block. #[serde(rename = "minfee")] pub minimum_fee: u64, @@ -658,53 +720,54 @@ pub struct GetBlockStats { pub minimum_fee_rate: u64, /// Minimum transaction size. #[serde(rename = "mintxsize")] - pub minimum_tx_size: u64, + pub minimum_tx_size: i64, /// The number of outputs. #[serde(rename = "outs")] - pub outputs: u64, + pub outputs: i64, /// The block subsidy. pub subsidy: u64, /// Total size of all segwit transactions. #[serde(rename = "swtotal_size")] - pub segwit_total_size: u64, + pub segwit_total_size: i64, /// Total weight of all segwit transactions divided by segwit scale factor (4). #[serde(rename = "swtotal_weight")] pub segwit_total_weight: u64, /// The number of segwit transactions. #[serde(rename = "swtxs")] - pub segwit_txs: u64, + pub segwit_txs: i64, /// The block time. - pub time: u32, + pub time: i64, /// Total amount in all outputs (excluding coinbase and thus reward [ie subsidy + totalfee]). pub total_out: u64, /// Total size of all non-coinbase transactions. - pub total_size: u64, + pub total_size: i64, /// Total weight of all non-coinbase transactions divided by segwit scale factor (4). pub total_weight: u64, /// The fee total. #[serde(rename = "totalfee")] pub total_fee: u64, /// The number of transactions (excluding coinbase). - pub txs: u64, + pub txs: i64, /// 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). #[serde(rename = "utxo_size_inc")] - pub utxo_size_increase: u64, + pub utxo_size_increase: i32, } impl GetBlockStats { /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> Result { - let block_hash = self.block_hash.parse::()?; + pub fn into_model(self) -> Result { + use GetBlockStatsError as E; + + // `FeeRate::sat_per_vb` returns an option if value overflows. + let average_fee_rate = FeeRate::from_sat_per_vb(self.average_fee_rate.into()); + let block_hash = self.block_hash.parse::().map_err(E::BlockHash)?; let fee_rate_percentiles = self .fee_rate_percentiles .iter() .map(|vb| FeeRate::from_sat_per_vb(*vb)) .collect::>>(); - - // `FeeRate::sat_per_vb` returns an option if value overflows. - let average_fee_rate = FeeRate::from_sat_per_vb(self.average_fee_rate); let max_fee_rate = FeeRate::from_sat_per_vb(self.max_fee_rate); let minimum_fee_rate = FeeRate::from_sat_per_vb(self.minimum_fee_rate); @@ -715,37 +778,72 @@ impl GetBlockStats { Ok(model::GetBlockStats { average_fee: Amount::from_sat(self.average_fee), average_fee_rate, - average_tx_size: self.average_tx_size, + average_tx_size: crate::to_u32(self.average_tx_size, "average_tx_size")?, block_hash, fee_rate_percentiles, - height: self.height, - inputs: self.inputs, + height: crate::to_u32(self.height, "height")?, + inputs: crate::to_u32(self.inputs, "inputs")?, max_fee: Amount::from_sat(self.max_fee), max_fee_rate, - max_tx_size: self.max_tx_size, + max_tx_size: crate::to_u32(self.max_tx_size, "max_tx_size")?, median_fee: Amount::from_sat(self.median_fee), - median_time: self.median_time, - median_tx_size: self.median_tx_size, + median_time: crate::to_u32(self.median_time, "median_time")?, + median_tx_size: crate::to_u32(self.median_tx_size, "median_tx_size")?, minimum_fee: Amount::from_sat(self.minimum_fee), minimum_fee_rate, - minimum_tx_size: self.minimum_tx_size, - outputs: self.outputs, + minimum_tx_size: crate::to_u32(self.minimum_tx_size, "minimum_tx_size")?, + outputs: crate::to_u32(self.outputs, "outputs")?, subsidy: Amount::from_sat(self.subsidy), - segwit_total_size: self.segwit_total_size, + segwit_total_size: crate::to_u32(self.segwit_total_size, "segwit_total_size")?, segwit_total_weight, - segwit_txs: self.segwit_txs, - time: self.time, + segwit_txs: crate::to_u32(self.segwit_txs, "segwit_txs")?, + time: crate::to_u32(self.time, "time")?, total_out: Amount::from_sat(self.total_out), - total_size: self.total_size, + total_size: crate::to_u32(self.total_size, "total_size")?, total_weight, total_fee: Amount::from_sat(self.total_fee), - txs: self.txs, + txs: crate::to_u32(self.txs, "txs")?, utxo_increase: self.utxo_increase, utxo_size_increase: self.utxo_size_increase, }) } } +/// Error when converting a `GetBlockStats` type into the model type. +#[derive(Debug)] +pub enum GetBlockStatsError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `block_hash` field failed. + BlockHash(hex::HexToArrayError), +} + +impl fmt::Display for GetBlockStatsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetBlockStatsError::*; + + match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), + BlockHash(ref e) => write_err!(f, "conversion of the `block_hash` field failed"; e), + } + } +} + +impl std::error::Error for GetBlockStatsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetBlockStatsError::*; + + match *self { + Numeric(ref e) => Some(e), + BlockHash(ref e) => Some(e), + } + } +} + +impl From for GetBlockStatsError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getchaintips`. /// /// > Return information about all known tips in the block tree, including the main chain as well as orphaned branches. @@ -756,12 +854,12 @@ pub struct GetChainTips(pub Vec); #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct ChainTips { /// Height of the chain tip. - pub height: u64, + pub height: i64, /// Block hash of the tip. pub hash: String, /// Zero for main chain. #[serde(rename = "branchlen")] - pub branch_length: u64, + pub branch_length: i64, /// "active" for the main chain. pub status: ChainTipsStatus, } @@ -784,7 +882,7 @@ pub enum ChainTipsStatus { impl GetChainTips { /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> Result { + pub fn into_model(self) -> Result { let v = self.0.into_iter().map(|item| item.into_model()).collect::, _>>()?; Ok(model::GetChainTips(v)) } @@ -792,13 +890,13 @@ impl GetChainTips { impl ChainTips { /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> Result { - let hash = self.hash.parse::()?; + pub fn into_model(self) -> Result { + use ChainTipsError as E; Ok(model::ChainTips { - height: self.height, - hash, - branch_length: self.branch_length, + height: crate::to_u32(self.height, "height")?, + hash: self.hash.parse::().map_err(E::Hash)?, + branch_length: crate::to_u32(self.branch_length, "branch_length")?, status: self.status.into_model(), }) } @@ -819,6 +917,41 @@ impl ChainTipsStatus { } } +/// Error when converting a `ChainTips` type into the model type. +#[derive(Debug)] +pub enum ChainTipsError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `hash` field failed. + Hash(hex::HexToArrayError), +} + +impl fmt::Display for ChainTipsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ChainTipsError::*; + + match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), + Hash(ref e) => write_err!(f, "conversion of the `hash` field failed"; e), + } + } +} + +impl std::error::Error for ChainTipsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ChainTipsError::*; + + match *self { + Numeric(ref e) => Some(e), + Hash(ref e) => Some(e), + } + } +} + +impl From for ChainTipsError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getchaintxstats`. /// /// > getchaintxstats ( nblocks blockhash ) @@ -831,40 +964,84 @@ impl ChainTipsStatus { #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub struct GetChainTxStats { /// The timestamp for the final block in the window in UNIX format. - pub time: u32, + pub time: i64, /// The total number of transactions in the chain up to that point. #[serde(rename = "txcount")] - pub tx_count: u64, + pub tx_count: i64, /// The hash of the final block in the window. pub window_final_block_hash: String, /// Size of the window in number of blocks. - pub window_block_count: u64, + pub window_block_count: i64, /// The number of transactions in the window. Only returned if "window_block_count" is > 0. - pub window_tx_count: Option, + pub window_tx_count: Option, /// The elapsed time in the window in seconds. Only returned if "window_block_count" is > 0. - pub window_interval: Option, + pub window_interval: Option, /// The average rate of transactions per second in the window. Only returned if "window_interval" is > 0. #[serde(rename = "txrate")] - pub tx_rate: Option, + pub tx_rate: Option, } impl GetChainTxStats { /// Converts version specific type to a version in-specific, more strongly typed type. - pub fn into_model(self) -> Result { - let window_final_block_hash = self.window_final_block_hash.parse::()?; + pub fn into_model(self) -> Result { + use GetChainTxStatsError as E; + + let window_final_block_hash = + self.window_final_block_hash.parse::().map_err(E::WindowFinalBlockHash)?; + let window_tx_count = + self.window_tx_count.map(|h| crate::to_u32(h, "window_tx_count")).transpose()?; + let window_interval = + self.window_interval.map(|h| crate::to_u32(h, "window_interval")).transpose()?; + let tx_rate = self.tx_rate.map(|h| crate::to_u32(h, "tx_rate")).transpose()?; Ok(model::GetChainTxStats { - time: self.time, - tx_count: self.tx_count, + time: crate::to_u32(self.time, "time")?, + tx_count: crate::to_u32(self.tx_count, "tx_count")?, window_final_block_hash, - window_block_count: self.window_block_count, - window_tx_count: self.window_tx_count, - window_interval: self.window_interval, - tx_rate: self.tx_rate, + window_block_count: crate::to_u32(self.window_block_count, "window_block_count")?, + window_tx_count, + window_interval, + tx_rate, }) } } +/// Error when converting a `GetChainTxStats` type into the model type. +#[derive(Debug)] +pub enum GetChainTxStatsError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `window_final_block_hash` field failed. + WindowFinalBlockHash(hex::HexToArrayError), +} + +impl fmt::Display for GetChainTxStatsError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use GetChainTxStatsError::*; + + match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), + WindowFinalBlockHash(ref e) => + write_err!(f, "conversion of the `window_final_block_hash` field failed"; e), + } + } +} + +impl std::error::Error for GetChainTxStatsError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use GetChainTxStatsError::*; + + match *self { + Numeric(ref e) => Some(e), + WindowFinalBlockHash(ref e) => Some(e), + } + } +} + +impl From for GetChainTxStatsError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} + /// Result of JSON-RPC method `getdifficulty`. /// /// > getdifficulty @@ -932,7 +1109,7 @@ pub struct GetTxOut { #[serde(rename = "bestblock")] pub best_block: String, /// The number of confirmations. - pub confirmations: u32, + pub confirmations: i64, /// The transaction value in BTC. pub value: f64, /// The script pubkey. @@ -967,7 +1144,6 @@ impl GetTxOut { use GetTxOutError as E; let best_block = self.best_block.parse::().map_err(E::BestBlock)?; - let tx_out = TxOut { value: Amount::from_btc(self.value).map_err(E::Value)?, script_pubkey: ScriptBuf::from_hex(&self.script_pubkey.hex).map_err(E::ScriptPubkey)?, @@ -988,6 +1164,8 @@ impl GetTxOut { /// Error when converting a `GetTxOut` type into the model type. #[derive(Debug)] pub enum GetTxOutError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), /// Conversion of the transaction `best_block` field failed. BestBlock(hex::HexToArrayError), /// Conversion of the transaction `value` field failed. @@ -1003,7 +1181,8 @@ impl fmt::Display for GetTxOutError { use GetTxOutError::*; match *self { - BestBlock(ref e) => write_err!(f, "conversion of the `best_block` field failed"; e), + Numeric(ref e) => write_err!(f, "numeric"; e), + BestBlock(ref e) => write_err!(f, "conversion of the `beast_block` field failed"; e), Value(ref e) => write_err!(f, "conversion of the `value` field failed"; e), ScriptPubkey(ref e) => write_err!(f, "conversion of the `script_pubkey` field failed"; e), @@ -1017,6 +1196,7 @@ impl std::error::Error for GetTxOutError { use GetTxOutError::*; match *self { + Numeric(ref e) => Some(e), BestBlock(ref e) => Some(e), Value(ref e) => Some(e), ScriptPubkey(ref e) => Some(e), @@ -1024,3 +1204,7 @@ impl std::error::Error for GetTxOutError { } } } + +impl From for GetTxOutError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +} diff --git a/json/src/v19/blockchain.rs b/json/src/v19/blockchain.rs index 6a23561..9be010f 100644 --- a/json/src/v19/blockchain.rs +++ b/json/src/v19/blockchain.rs @@ -12,7 +12,7 @@ use bitcoin::{hex, network, BlockHash, Network, Work}; use internals::write_err; use serde::{Deserialize, Serialize}; -use crate::model; +use crate::{model, NumericError}; #[rustfmt::skip] // Keep public re-exports separate. @@ -26,9 +26,9 @@ pub struct GetBlockchainInfo { /// Current network name as defined in BIP70 (main, test, signet, regtest). pub chain: String, /// The current number of blocks processed in the server. - pub blocks: u64, + pub blocks: i64, /// The current number of headers we have validated. - pub headers: u64, + pub headers: i64, /// The hash of the currently best block. #[serde(rename = "bestblockhash")] pub best_block_hash: String, @@ -36,7 +36,7 @@ pub struct GetBlockchainInfo { pub difficulty: f64, /// Median time for the current best block. #[serde(rename = "mediantime")] - pub median_time: u64, + pub median_time: i64, /// Estimate of verification progress (between 0 and 1). #[serde(rename = "verificationprogress")] pub verification_progress: f64, @@ -52,11 +52,11 @@ pub struct GetBlockchainInfo { pub pruned: bool, /// Lowest-height complete block stored (only present if pruning is enabled). #[serde(rename = "pruneheight")] - pub prune_height: Option, + pub prune_height: Option, /// Whether automatic pruning is enabled (only present if pruning is enabled). pub automatic_pruning: Option, /// The target size used by pruning (only present if automatic pruning is enabled). - pub prune_target_size: Option, + pub prune_target_size: Option, /// Status of softforks in progress, maps softfork name -> [`Softfork`]. #[serde(default)] pub softforks: BTreeMap, @@ -73,7 +73,7 @@ pub struct Softfork { /// The status of bip9 softforks (only for "bip9" type). pub bip9: Option, /// 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, + pub height: Option, /// `true` if the rules are enforced for the mempool and the next block. pub active: bool, } @@ -102,9 +102,9 @@ pub struct Bip9SoftforkInfo { /// The minimum median time past of a block at which the bit gains its meaning. pub start_time: i64, /// The median time past of a block at which the deployment is considered failed if not yet locked in. - pub timeout: u64, + pub timeout: i64, /// Height of the first block to which the status applies. - pub since: u32, + pub since: i64, /// Numeric statistics about BIP-9 signalling for a softfork (only for "started" status). pub statistics: Option, } @@ -129,13 +129,13 @@ pub enum Bip9SoftforkStatus { #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)] pub struct Bip9SoftforkStatistics { /// The length in blocks of the BIP9 signalling period. - pub period: u32, + pub period: i64, /// The number of blocks with the version bit set required to activate the feature. - pub threshold: Option, + pub threshold: Option, /// The number of blocks elapsed since the beginning of the current period. - pub elapsed: u32, + pub elapsed: i64, /// The number of blocks with the version bit set in the current period. - pub count: u32, + pub count: i64, /// `false` if there are not enough blocks left in this period to pass activation threshold. pub possible: Option, } @@ -149,24 +149,27 @@ impl GetBlockchainInfo { let best_block_hash = self.best_block_hash.parse::().map_err(E::BestBlockHash)?; let chain_work = Work::from_unprefixed_hex(&self.chain_work).map_err(E::ChainWork)?; - + let prune_height = + self.prune_height.map(|h| crate::to_u32(h, "prune_height")).transpose()?; + let prune_target_size = + self.prune_target_size.map(|h| crate::to_u32(h, "prune_target_size")).transpose()?; let softforks = BTreeMap::new(); // TODO: Handle softforks stuff. Ok(model::GetBlockchainInfo { chain, - blocks: self.blocks, - headers: self.headers, + blocks: crate::to_u32(self.blocks, "blocks")?, + headers: crate::to_u32(self.headers, "headers")?, best_block_hash, difficulty: self.difficulty, - median_time: self.median_time, + median_time: crate::to_u32(self.median_time, "median_time")?, verification_progress: self.verification_progress, initial_block_download: self.initial_block_download, chain_work, size_on_disk: self.size_on_disk, pruned: self.pruned, - prune_height: self.prune_height, + prune_height, automatic_pruning: self.automatic_pruning, - prune_target_size: self.prune_target_size, + prune_target_size, softforks, warnings: self.warnings, }) @@ -174,10 +177,15 @@ impl GetBlockchainInfo { } /// Error when converting a `GetBlockchainInfo` type into the model type. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug)] pub enum GetBlockchainInfoError { + /// Conversion of numeric type to expected type failed. + Numeric(NumericError), + /// Conversion of the `chain` field failed. Chain(network::ParseNetworkError), + /// Conversion of the `best_block_hash` field failed. BestBlockHash(hex::HexToArrayError), + /// Conversion of the `chain_work` field failed. ChainWork(UnprefixedHexError), } @@ -186,6 +194,7 @@ impl fmt::Display for GetBlockchainInfoError { use GetBlockchainInfoError::*; match *self { + Numeric(ref e) => write_err!(f, "numeric"; e), Chain(ref e) => write_err!(f, "conversion of the `chain` field failed"; e), BestBlockHash(ref e) => { write_err!(f, "conversion of the `best_block_hash` field failed"; e) @@ -200,9 +209,14 @@ impl std::error::Error for GetBlockchainInfoError { use GetBlockchainInfoError::*; match *self { + Numeric(ref e) => Some(e), Chain(ref e) => Some(e), BestBlockHash(ref e) => Some(e), ChainWork(ref e) => Some(e), } } } + +impl From for GetBlockchainInfoError { + fn from(e: NumericError) -> Self { Self::Numeric(e) } +}