add(rpc): Add block times to verbose output of `getblock` RPC method (#8384)
* Returns block times from the getblock RPC when used with verbosity = 1 (it's already included with verbosity = 0 but this makes it easier to use). * cleanup/refactor, adds MapServerError and OkOrServerError traits * moves rpc error conversion traits to their own module * Only returns block time for verbosity = 2, updates snapshots
This commit is contained in:
parent
4241e320b8
commit
ad34585590
|
|
@ -38,6 +38,10 @@ use crate::{
|
||||||
queue::Queue,
|
queue::Queue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod errors;
|
||||||
|
|
||||||
|
use errors::{MapServerError, OkOrServerError};
|
||||||
|
|
||||||
// We don't use a types/ module here, because it is redundant.
|
// We don't use a types/ module here, because it is redundant.
|
||||||
pub mod trees;
|
pub mod trees;
|
||||||
|
|
||||||
|
|
@ -675,7 +679,6 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - use a generic error constructor (#5548)
|
|
||||||
// - use `height_from_signed_int()` to handle negative heights
|
// - use `height_from_signed_int()` to handle negative heights
|
||||||
// (this might be better in the state request, because it needs the state height)
|
// (this might be better in the state request, because it needs the state height)
|
||||||
// - create a function that handles block hashes or heights, and use it in `z_get_treestate()`
|
// - create a function that handles block hashes or heights, and use it in `z_get_treestate()`
|
||||||
|
|
@ -727,7 +730,7 @@ where
|
||||||
}),
|
}),
|
||||||
_ => unreachable!("unmatched response to a block request"),
|
_ => unreachable!("unmatched response to a block request"),
|
||||||
}
|
}
|
||||||
} else if verbosity == 1 {
|
} else if verbosity == 1 || verbosity == 2 {
|
||||||
// # Performance
|
// # Performance
|
||||||
//
|
//
|
||||||
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
|
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
|
||||||
|
|
@ -748,6 +751,8 @@ where
|
||||||
// must be able to handle chain forks, including a hash for a block that is
|
// must be able to handle chain forks, including a hash for a block that is
|
||||||
// later discovered to be on a side chain.
|
// later discovered to be on a side chain.
|
||||||
|
|
||||||
|
let should_read_block_header = verbosity == 2;
|
||||||
|
|
||||||
let hash = match hash_or_height {
|
let hash = match hash_or_height {
|
||||||
HashOrHeight::Hash(hash) => hash,
|
HashOrHeight::Hash(hash) => hash,
|
||||||
HashOrHeight::Height(height) => {
|
HashOrHeight::Height(height) => {
|
||||||
|
|
@ -776,120 +781,80 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get transaction IDs from the transaction index by block hash
|
// TODO: look up the height if we only have a hash,
|
||||||
//
|
// this needs a new state request for the height -> hash index
|
||||||
// # Concurrency
|
let height = hash_or_height.height();
|
||||||
//
|
|
||||||
// We look up by block hash so the hash, transaction IDs, and confirmations
|
|
||||||
// are consistent.
|
|
||||||
//
|
|
||||||
// A block's transaction IDs are never modified, so all possible responses are
|
|
||||||
// valid. Clients that query block heights must be able to handle chain forks,
|
|
||||||
// including getting transaction IDs from any chain fork.
|
|
||||||
let request = zebra_state::ReadRequest::TransactionIdsForBlock(hash.into());
|
|
||||||
let tx_ids_response_fut = state.clone().oneshot(request);
|
|
||||||
|
|
||||||
// Get block confirmations from the block height index
|
|
||||||
//
|
|
||||||
// # Concurrency
|
// # Concurrency
|
||||||
//
|
//
|
||||||
// We look up by block hash so the hash, transaction IDs, and confirmations
|
// We look up by block hash so the hash, transaction IDs, and confirmations
|
||||||
// are consistent.
|
// are consistent.
|
||||||
//
|
let mut requests = vec![
|
||||||
// All possible responses are valid, even if a block is added to the chain, or
|
// Get transaction IDs from the transaction index by block hash
|
||||||
// the best chain changes. Clients must be able to handle chain forks, including
|
//
|
||||||
// different confirmation values before or after added blocks, and switching
|
// # Concurrency
|
||||||
// between -1 and multiple different confirmation values.
|
//
|
||||||
|
// A block's transaction IDs are never modified, so all possible responses are
|
||||||
|
// valid. Clients that query block heights must be able to handle chain forks,
|
||||||
|
// including getting transaction IDs from any chain fork.
|
||||||
|
zebra_state::ReadRequest::TransactionIdsForBlock(hash.into()),
|
||||||
|
// Sapling trees
|
||||||
|
zebra_state::ReadRequest::SaplingTree(hash.into()),
|
||||||
|
// Orchard trees
|
||||||
|
zebra_state::ReadRequest::OrchardTree(hash.into()),
|
||||||
|
// Get block confirmations from the block height index
|
||||||
|
//
|
||||||
|
// # Concurrency
|
||||||
|
//
|
||||||
|
// All possible responses are valid, even if a block is added to the chain, or
|
||||||
|
// the best chain changes. Clients must be able to handle chain forks, including
|
||||||
|
// different confirmation values before or after added blocks, and switching
|
||||||
|
// between -1 and multiple different confirmation values.
|
||||||
|
zebra_state::ReadRequest::Depth(hash),
|
||||||
|
];
|
||||||
|
|
||||||
|
if should_read_block_header {
|
||||||
|
// Block header
|
||||||
|
requests.push(zebra_state::ReadRequest::BlockHeader(hash.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut futs = FuturesOrdered::new();
|
||||||
|
|
||||||
|
for request in requests {
|
||||||
|
futs.push_back(state.clone().oneshot(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
|
||||||
|
let tx = match tx_ids_response.map_server_error()? {
|
||||||
|
zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => tx_ids
|
||||||
|
.ok_or_server_error("Block not found")?
|
||||||
|
.iter()
|
||||||
|
.map(|tx_id| tx_id.encode_hex())
|
||||||
|
.collect(),
|
||||||
|
_ => unreachable!("unmatched response to a transaction_ids_for_block request"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let sapling_tree_response = futs.next().await.expect("`futs` should not be empty");
|
||||||
|
let sapling_note_commitment_tree_count =
|
||||||
|
match sapling_tree_response.map_server_error()? {
|
||||||
|
zebra_state::ReadResponse::SaplingTree(Some(nct)) => nct.count(),
|
||||||
|
zebra_state::ReadResponse::SaplingTree(None) => 0,
|
||||||
|
_ => unreachable!("unmatched response to a SaplingTree request"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let orchard_tree_response = futs.next().await.expect("`futs` should not be empty");
|
||||||
|
let orchard_note_commitment_tree_count =
|
||||||
|
match orchard_tree_response.map_server_error()? {
|
||||||
|
zebra_state::ReadResponse::OrchardTree(Some(nct)) => nct.count(),
|
||||||
|
zebra_state::ReadResponse::OrchardTree(None) => 0,
|
||||||
|
_ => unreachable!("unmatched response to a OrchardTree request"),
|
||||||
|
};
|
||||||
|
|
||||||
// From <https://zcash.github.io/rpc/getblock.html>
|
// From <https://zcash.github.io/rpc/getblock.html>
|
||||||
const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
|
const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;
|
||||||
|
|
||||||
let request = zebra_state::ReadRequest::Depth(hash);
|
let depth_response = futs.next().await.expect("`futs` should not be empty");
|
||||||
let depth_response_fut = state.clone().oneshot(request);
|
let confirmations = match depth_response.map_server_error()? {
|
||||||
|
|
||||||
// Sapling trees
|
|
||||||
//
|
|
||||||
// # Concurrency
|
|
||||||
//
|
|
||||||
// We look up by block hash so the hash, transaction IDs, and confirmations
|
|
||||||
// are consistent.
|
|
||||||
let request = zebra_state::ReadRequest::SaplingTree(hash.into());
|
|
||||||
let sapling_tree_response_fut = state.clone().oneshot(request);
|
|
||||||
|
|
||||||
// Orchard trees
|
|
||||||
//
|
|
||||||
// # Concurrency
|
|
||||||
//
|
|
||||||
// We look up by block hash so the hash, transaction IDs, and confirmations
|
|
||||||
// are consistent.
|
|
||||||
let request = zebra_state::ReadRequest::OrchardTree(hash.into());
|
|
||||||
let orchard_tree_response_fut = state.clone().oneshot(request);
|
|
||||||
|
|
||||||
let mut futs = FuturesOrdered::new();
|
|
||||||
futs.push_back(tx_ids_response_fut);
|
|
||||||
futs.push_back(sapling_tree_response_fut);
|
|
||||||
futs.push_back(orchard_tree_response_fut);
|
|
||||||
futs.push_back(depth_response_fut);
|
|
||||||
|
|
||||||
let tx_ids_response = futs
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.expect("should have 4 items in futs")
|
|
||||||
.map_err(|error| Error {
|
|
||||||
code: ErrorCode::ServerError(0),
|
|
||||||
message: error.to_string(),
|
|
||||||
data: None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let sapling_tree_response = futs
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.expect("should have 3 items in futs")
|
|
||||||
.map_err(|error| Error {
|
|
||||||
code: ErrorCode::ServerError(0),
|
|
||||||
message: error.to_string(),
|
|
||||||
data: None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let orchard_tree_response = futs
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.expect("should have 2 items in futs")
|
|
||||||
.map_err(|error| Error {
|
|
||||||
code: ErrorCode::ServerError(0),
|
|
||||||
message: error.to_string(),
|
|
||||||
data: None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let depth_response = futs
|
|
||||||
.next()
|
|
||||||
.await
|
|
||||||
.expect("should have an item in futs")
|
|
||||||
.map_err(|error| Error {
|
|
||||||
code: ErrorCode::ServerError(0),
|
|
||||||
message: error.to_string(),
|
|
||||||
data: None,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let tx = match tx_ids_response {
|
|
||||||
zebra_state::ReadResponse::TransactionIdsForBlock(Some(tx_ids)) => {
|
|
||||||
tx_ids.iter().map(|tx_id| tx_id.encode_hex()).collect()
|
|
||||||
}
|
|
||||||
zebra_state::ReadResponse::TransactionIdsForBlock(None) => Err(Error {
|
|
||||||
code: MISSING_BLOCK_ERROR_CODE,
|
|
||||||
message: "Block not found".to_string(),
|
|
||||||
data: None,
|
|
||||||
})?,
|
|
||||||
_ => unreachable!("unmatched response to a transaction_ids_for_block request"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let sapling_note_commitment_tree_count = match sapling_tree_response {
|
|
||||||
zebra_state::ReadResponse::SaplingTree(Some(nct)) => nct.count(),
|
|
||||||
zebra_state::ReadResponse::SaplingTree(None) => 0,
|
|
||||||
_ => unreachable!("unmatched response to a SaplingTree request"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let confirmations = match depth_response {
|
|
||||||
// Confirmations are one more than the depth.
|
// Confirmations are one more than the depth.
|
||||||
// Depth is limited by height, so it will never overflow an i64.
|
// Depth is limited by height, so it will never overflow an i64.
|
||||||
zebra_state::ReadResponse::Depth(Some(depth)) => i64::from(depth) + 1,
|
zebra_state::ReadResponse::Depth(Some(depth)) => i64::from(depth) + 1,
|
||||||
|
|
@ -897,14 +862,21 @@ where
|
||||||
_ => unreachable!("unmatched response to a depth request"),
|
_ => unreachable!("unmatched response to a depth request"),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: look up the height if we only have a hash,
|
let time = if should_read_block_header {
|
||||||
// this needs a new state request for the height -> hash index
|
let block_header_response =
|
||||||
let height = hash_or_height.height();
|
futs.next().await.expect("`futs` should not be empty");
|
||||||
|
|
||||||
let orchard_note_commitment_tree_count = match orchard_tree_response {
|
match block_header_response.map_server_error()? {
|
||||||
zebra_state::ReadResponse::OrchardTree(Some(nct)) => nct.count(),
|
zebra_state::ReadResponse::BlockHeader(header) => Some(
|
||||||
zebra_state::ReadResponse::OrchardTree(None) => 0,
|
header
|
||||||
_ => unreachable!("unmatched response to a OrchardTree request"),
|
.ok_or_server_error("Block not found")?
|
||||||
|
.time
|
||||||
|
.timestamp(),
|
||||||
|
),
|
||||||
|
_ => unreachable!("unmatched response to a BlockHeader request"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let sapling = SaplingTrees {
|
let sapling = SaplingTrees {
|
||||||
|
|
@ -921,6 +893,7 @@ where
|
||||||
hash: GetBlockHash(hash),
|
hash: GetBlockHash(hash),
|
||||||
confirmations,
|
confirmations,
|
||||||
height,
|
height,
|
||||||
|
time,
|
||||||
tx,
|
tx,
|
||||||
trees,
|
trees,
|
||||||
})
|
})
|
||||||
|
|
@ -1639,6 +1612,10 @@ pub enum GetBlock {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
height: Option<Height>,
|
height: Option<Height>,
|
||||||
|
|
||||||
|
/// The height of the requested block.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
time: Option<i64>,
|
||||||
|
|
||||||
/// List of transaction IDs in block order, hex-encoded.
|
/// List of transaction IDs in block order, hex-encoded.
|
||||||
//
|
//
|
||||||
// TODO: use a typed Vec<transaction::Hash> here
|
// TODO: use a typed Vec<transaction::Hash> here
|
||||||
|
|
@ -1655,6 +1632,7 @@ impl Default for GetBlock {
|
||||||
hash: GetBlockHash::default(),
|
hash: GetBlockHash::default(),
|
||||||
confirmations: 0,
|
confirmations: 0,
|
||||||
height: None,
|
height: None,
|
||||||
|
time: None,
|
||||||
tx: Vec::new(),
|
tx: Vec::new(),
|
||||||
trees: GetBlockTrees::default(),
|
trees: GetBlockTrees::default(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
//! Error conversions for Zebra's RPC methods.
|
||||||
|
|
||||||
|
use jsonrpc_core::ErrorCode;
|
||||||
|
|
||||||
|
pub(crate) trait MapServerError<T, E> {
|
||||||
|
fn map_server_error(self) -> std::result::Result<T, jsonrpc_core::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait OkOrServerError<T> {
|
||||||
|
fn ok_or_server_error<S: ToString>(
|
||||||
|
self,
|
||||||
|
message: S,
|
||||||
|
) -> std::result::Result<T, jsonrpc_core::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> MapServerError<T, E> for Result<T, E>
|
||||||
|
where
|
||||||
|
E: ToString,
|
||||||
|
{
|
||||||
|
fn map_server_error(self) -> Result<T, jsonrpc_core::Error> {
|
||||||
|
self.map_err(|error| jsonrpc_core::Error {
|
||||||
|
code: ErrorCode::ServerError(0),
|
||||||
|
message: error.to_string(),
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OkOrServerError<T> for Option<T> {
|
||||||
|
fn ok_or_server_error<S: ToString>(self, message: S) -> Result<T, jsonrpc_core::Error> {
|
||||||
|
self.ok_or(jsonrpc_core::Error {
|
||||||
|
code: ErrorCode::ServerError(0),
|
||||||
|
message: message.to_string(),
|
||||||
|
data: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! To update these snapshots, run:
|
//! To update these snapshots, run:
|
||||||
//! ```sh
|
//! ```sh
|
||||||
//! cargo insta test --review
|
//! cargo insta test --review -p zebra-rpc --lib -- test_rpc_response_data
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
use std::{collections::BTreeMap, sync::Arc};
|
use std::{collections::BTreeMap, sync::Arc};
|
||||||
|
|
@ -167,6 +167,25 @@ async fn test_rpc_response_data_for_network(network: &Network) {
|
||||||
.expect("We should have a GetBlock struct");
|
.expect("We should have a GetBlock struct");
|
||||||
snapshot_rpc_getblock_verbose("hash_verbosity_1", get_block, &settings);
|
snapshot_rpc_getblock_verbose("hash_verbosity_1", get_block, &settings);
|
||||||
|
|
||||||
|
// `getblock`, verbosity=2, height
|
||||||
|
let get_block = rpc
|
||||||
|
.get_block(BLOCK_HEIGHT.to_string(), Some(2u8))
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlock struct");
|
||||||
|
snapshot_rpc_getblock_verbose("height_verbosity_2", get_block, &settings);
|
||||||
|
|
||||||
|
let get_block = rpc
|
||||||
|
.get_block(EXCESSIVE_BLOCK_HEIGHT.to_string(), Some(2u8))
|
||||||
|
.await;
|
||||||
|
snapshot_rpc_getblock_invalid("excessive_height_verbosity_2", get_block, &settings);
|
||||||
|
|
||||||
|
// `getblock`, verbosity=2, hash
|
||||||
|
let get_block = rpc
|
||||||
|
.get_block(block_hash.to_string(), Some(2u8))
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlock struct");
|
||||||
|
snapshot_rpc_getblock_verbose("hash_verbosity_2", get_block, &settings);
|
||||||
|
|
||||||
// `getblock`, no verbosity - defaults to 1, height
|
// `getblock`, no verbosity - defaults to 1, height
|
||||||
let get_block = rpc
|
let get_block = rpc
|
||||||
.get_block(BLOCK_HEIGHT.to_string(), None)
|
.get_block(BLOCK_HEIGHT.to_string(), None)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: response
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Err": {
|
||||||
|
"code": -8,
|
||||||
|
"message": "block height not in best chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: response
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"Err": {
|
||||||
|
"code": -8,
|
||||||
|
"message": "block height not in best chain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: block
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
|
||||||
|
"confirmations": 10,
|
||||||
|
"time": 1477671596,
|
||||||
|
"tx": [
|
||||||
|
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
|
||||||
|
],
|
||||||
|
"trees": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: block
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
|
||||||
|
"confirmations": 10,
|
||||||
|
"time": 1477674473,
|
||||||
|
"tx": [
|
||||||
|
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
|
||||||
|
],
|
||||||
|
"trees": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: block
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
|
||||||
|
"confirmations": 10,
|
||||||
|
"height": 1,
|
||||||
|
"time": 1477671596,
|
||||||
|
"tx": [
|
||||||
|
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
|
||||||
|
],
|
||||||
|
"trees": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
---
|
||||||
|
source: zebra-rpc/src/methods/tests/snapshot.rs
|
||||||
|
expression: block
|
||||||
|
---
|
||||||
|
{
|
||||||
|
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
|
||||||
|
"confirmations": 10,
|
||||||
|
"height": 1,
|
||||||
|
"time": 1477674473,
|
||||||
|
"tx": [
|
||||||
|
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
|
||||||
|
],
|
||||||
|
"trees": {}
|
||||||
|
}
|
||||||
|
|
@ -140,6 +140,7 @@ async fn rpc_getblock() {
|
||||||
hash: GetBlockHash(block.hash()),
|
hash: GetBlockHash(block.hash()),
|
||||||
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
height: Some(Height(i.try_into().expect("valid u32"))),
|
height: Some(Height(i.try_into().expect("valid u32"))),
|
||||||
|
time: None,
|
||||||
tx: block
|
tx: block
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -163,6 +164,55 @@ async fn rpc_getblock() {
|
||||||
hash: GetBlockHash(block.hash()),
|
hash: GetBlockHash(block.hash()),
|
||||||
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
height: None,
|
height: None,
|
||||||
|
time: None,
|
||||||
|
tx: block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.map(|tx| tx.hash().encode_hex())
|
||||||
|
.collect(),
|
||||||
|
trees,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make height calls with verbosity=2 and check response
|
||||||
|
for (i, block) in blocks.iter().enumerate() {
|
||||||
|
let get_block = rpc
|
||||||
|
.get_block(i.to_string(), Some(2u8))
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlock struct");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_block,
|
||||||
|
GetBlock::Object {
|
||||||
|
hash: GetBlockHash(block.hash()),
|
||||||
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
|
height: Some(Height(i.try_into().expect("valid u32"))),
|
||||||
|
time: Some(block.header.time.timestamp()),
|
||||||
|
tx: block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.map(|tx| tx.hash().encode_hex())
|
||||||
|
.collect(),
|
||||||
|
trees,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make hash calls with verbosity=2 and check response
|
||||||
|
for (i, block) in blocks.iter().enumerate() {
|
||||||
|
let get_block = rpc
|
||||||
|
.get_block(blocks[i].hash().to_string(), Some(2u8))
|
||||||
|
.await
|
||||||
|
.expect("We should have a GetBlock struct");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
get_block,
|
||||||
|
GetBlock::Object {
|
||||||
|
hash: GetBlockHash(block.hash()),
|
||||||
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
|
height: None,
|
||||||
|
time: Some(block.header.time.timestamp()),
|
||||||
tx: block
|
tx: block
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -186,6 +236,7 @@ async fn rpc_getblock() {
|
||||||
hash: GetBlockHash(block.hash()),
|
hash: GetBlockHash(block.hash()),
|
||||||
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
height: Some(Height(i.try_into().expect("valid u32"))),
|
height: Some(Height(i.try_into().expect("valid u32"))),
|
||||||
|
time: None,
|
||||||
tx: block
|
tx: block
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -209,6 +260,7 @@ async fn rpc_getblock() {
|
||||||
hash: GetBlockHash(block.hash()),
|
hash: GetBlockHash(block.hash()),
|
||||||
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
confirmations: (blocks.len() - i).try_into().expect("valid i64"),
|
||||||
height: None,
|
height: None,
|
||||||
|
time: None,
|
||||||
tx: block
|
tx: block
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
|
||||||
|
|
@ -614,6 +614,16 @@ pub enum Request {
|
||||||
/// [`block::Height`] using `.into()`.
|
/// [`block::Height`] using `.into()`.
|
||||||
Block(HashOrHeight),
|
Block(HashOrHeight),
|
||||||
|
|
||||||
|
/// Looks up a block header by hash or height in the current best chain.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// [`Response::BlockHeader(block::Header)`](Response::BlockHeader).
|
||||||
|
///
|
||||||
|
/// Note: the [`HashOrHeight`] can be constructed from a [`block::Hash`] or
|
||||||
|
/// [`block::Height`] using `.into()`.
|
||||||
|
BlockHeader(HashOrHeight),
|
||||||
|
|
||||||
/// Request a UTXO identified by the given [`OutPoint`](transparent::OutPoint),
|
/// Request a UTXO identified by the given [`OutPoint`](transparent::OutPoint),
|
||||||
/// waiting until it becomes available if it is unknown.
|
/// waiting until it becomes available if it is unknown.
|
||||||
///
|
///
|
||||||
|
|
@ -725,6 +735,7 @@ impl Request {
|
||||||
Request::Transaction(_) => "transaction",
|
Request::Transaction(_) => "transaction",
|
||||||
Request::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
|
Request::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
|
||||||
Request::Block(_) => "block",
|
Request::Block(_) => "block",
|
||||||
|
Request::BlockHeader(_) => "block_header",
|
||||||
Request::FindBlockHashes { .. } => "find_block_hashes",
|
Request::FindBlockHashes { .. } => "find_block_hashes",
|
||||||
Request::FindBlockHeaders { .. } => "find_block_headers",
|
Request::FindBlockHeaders { .. } => "find_block_headers",
|
||||||
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
||||||
|
|
@ -776,6 +787,16 @@ pub enum ReadRequest {
|
||||||
/// [`block::Height`] using `.into()`.
|
/// [`block::Height`] using `.into()`.
|
||||||
Block(HashOrHeight),
|
Block(HashOrHeight),
|
||||||
|
|
||||||
|
/// Looks up a block header by hash or height in the current best chain.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// [`Response::BlockHeader(block::Header)`](Response::BlockHeader).
|
||||||
|
///
|
||||||
|
/// Note: the [`HashOrHeight`] can be constructed from a [`block::Hash`] or
|
||||||
|
/// [`block::Height`] using `.into()`.
|
||||||
|
BlockHeader(HashOrHeight),
|
||||||
|
|
||||||
/// Looks up a transaction by hash in the current best chain.
|
/// Looks up a transaction by hash in the current best chain.
|
||||||
///
|
///
|
||||||
/// Returns
|
/// Returns
|
||||||
|
|
@ -999,6 +1020,7 @@ impl ReadRequest {
|
||||||
ReadRequest::Tip => "tip",
|
ReadRequest::Tip => "tip",
|
||||||
ReadRequest::Depth(_) => "depth",
|
ReadRequest::Depth(_) => "depth",
|
||||||
ReadRequest::Block(_) => "block",
|
ReadRequest::Block(_) => "block",
|
||||||
|
ReadRequest::BlockHeader(_) => "block_header",
|
||||||
ReadRequest::Transaction(_) => "transaction",
|
ReadRequest::Transaction(_) => "transaction",
|
||||||
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",
|
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",
|
||||||
ReadRequest::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
|
ReadRequest::UnspentBestChainUtxo { .. } => "unspent_best_chain_utxo",
|
||||||
|
|
@ -1052,6 +1074,7 @@ impl TryFrom<Request> for ReadRequest {
|
||||||
Request::BestChainBlockHash(hash) => Ok(ReadRequest::BestChainBlockHash(hash)),
|
Request::BestChainBlockHash(hash) => Ok(ReadRequest::BestChainBlockHash(hash)),
|
||||||
|
|
||||||
Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)),
|
Request::Block(hash_or_height) => Ok(ReadRequest::Block(hash_or_height)),
|
||||||
|
Request::BlockHeader(hash_or_height) => Ok(ReadRequest::BlockHeader(hash_or_height)),
|
||||||
Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)),
|
Request::Transaction(tx_hash) => Ok(ReadRequest::Transaction(tx_hash)),
|
||||||
Request::UnspentBestChainUtxo(outpoint) => {
|
Request::UnspentBestChainUtxo(outpoint) => {
|
||||||
Ok(ReadRequest::UnspentBestChainUtxo(outpoint))
|
Ok(ReadRequest::UnspentBestChainUtxo(outpoint))
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,9 @@ pub enum Response {
|
||||||
/// Response to [`Request::Block`] with the specified block.
|
/// Response to [`Request::Block`] with the specified block.
|
||||||
Block(Option<Arc<Block>>),
|
Block(Option<Arc<Block>>),
|
||||||
|
|
||||||
|
/// The response to a `BlockHeader` request.
|
||||||
|
BlockHeader(Option<Arc<block::Header>>),
|
||||||
|
|
||||||
/// The response to a `AwaitUtxo` request, from any non-finalized chains, finalized chain,
|
/// The response to a `AwaitUtxo` request, from any non-finalized chains, finalized chain,
|
||||||
/// pending unverified blocks, or blocks received after the request was sent.
|
/// pending unverified blocks, or blocks received after the request was sent.
|
||||||
Utxo(transparent::Utxo),
|
Utxo(transparent::Utxo),
|
||||||
|
|
@ -131,6 +134,9 @@ pub enum ReadResponse {
|
||||||
/// Response to [`ReadRequest::Block`] with the specified block.
|
/// Response to [`ReadRequest::Block`] with the specified block.
|
||||||
Block(Option<Arc<Block>>),
|
Block(Option<Arc<Block>>),
|
||||||
|
|
||||||
|
/// The response to a `BlockHeader` request.
|
||||||
|
BlockHeader(Option<Arc<block::Header>>),
|
||||||
|
|
||||||
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
||||||
Transaction(Option<MinedTx>),
|
Transaction(Option<MinedTx>),
|
||||||
|
|
||||||
|
|
@ -265,6 +271,7 @@ impl TryFrom<ReadResponse> for Response {
|
||||||
ReadResponse::BlockHash(hash) => Ok(Response::BlockHash(hash)),
|
ReadResponse::BlockHash(hash) => Ok(Response::BlockHash(hash)),
|
||||||
|
|
||||||
ReadResponse::Block(block) => Ok(Response::Block(block)),
|
ReadResponse::Block(block) => Ok(Response::Block(block)),
|
||||||
|
ReadResponse::BlockHeader(header) => Ok(Response::BlockHeader(header)),
|
||||||
ReadResponse::Transaction(tx_info) => {
|
ReadResponse::Transaction(tx_info) => {
|
||||||
Ok(Response::Transaction(tx_info.map(|tx_info| tx_info.tx)))
|
Ok(Response::Transaction(tx_info.map(|tx_info| tx_info.tx)))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1120,6 +1120,7 @@ impl Service<Request> for StateService {
|
||||||
| Request::Transaction(_)
|
| Request::Transaction(_)
|
||||||
| Request::UnspentBestChainUtxo(_)
|
| Request::UnspentBestChainUtxo(_)
|
||||||
| Request::Block(_)
|
| Request::Block(_)
|
||||||
|
| Request::BlockHeader(_)
|
||||||
| Request::FindBlockHashes { .. }
|
| Request::FindBlockHashes { .. }
|
||||||
| Request::FindBlockHeaders { .. }
|
| Request::FindBlockHeaders { .. }
|
||||||
| Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
| Request::CheckBestChainTipNullifiersAndAnchors(_) => {
|
||||||
|
|
@ -1288,6 +1289,31 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.wait_for_panics()
|
.wait_for_panics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by the get_block (verbose) RPC and the StateService.
|
||||||
|
ReadRequest::BlockHeader(hash_or_height) => {
|
||||||
|
let state = self.clone();
|
||||||
|
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
span.in_scope(move || {
|
||||||
|
let header = state.non_finalized_state_receiver.with_watch_data(
|
||||||
|
|non_finalized_state| {
|
||||||
|
read::block_header(
|
||||||
|
non_finalized_state.best_chain(),
|
||||||
|
&state.db,
|
||||||
|
hash_or_height,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The work is done in the future.
|
||||||
|
timer.finish(module_path!(), line!(), "ReadRequest::Block");
|
||||||
|
|
||||||
|
Ok(ReadResponse::BlockHeader(header))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.wait_for_panics()
|
||||||
|
}
|
||||||
|
|
||||||
// For the get_raw_transaction RPC and the StateService.
|
// For the get_raw_transaction RPC and the StateService.
|
||||||
ReadRequest::Transaction(hash) => {
|
ReadRequest::Transaction(hash) => {
|
||||||
let state = self.clone();
|
let state = self.clone();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue