1. fix(rpc): Fix slow getblock RPC (verbose=1) using transaction ID index (#5307)
* Add RPC timing to zcash-rpc-diff * Use transaction hash index for verbose block requests, rather than block data
This commit is contained in:
parent
75a679792b
commit
211dbb437b
|
|
@ -540,46 +540,65 @@ where
|
||||||
let mut state = self.state.clone();
|
let mut state = self.state.clone();
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let height = height.parse().map_err(|error: SerializationError| Error {
|
let height: Height = height.parse().map_err(|error: SerializationError| Error {
|
||||||
code: ErrorCode::ServerError(0),
|
code: ErrorCode::ServerError(0),
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let request =
|
if verbosity == 0 {
|
||||||
zebra_state::ReadRequest::Block(zebra_state::HashOrHeight::Height(height));
|
let request = zebra_state::ReadRequest::Block(height.into());
|
||||||
let response = state
|
let response = state
|
||||||
.ready()
|
.ready()
|
||||||
.and_then(|service| service.call(request))
|
.and_then(|service| service.call(request))
|
||||||
.await
|
.await
|
||||||
.map_err(|error| Error {
|
.map_err(|error| Error {
|
||||||
code: ErrorCode::ServerError(0),
|
code: ErrorCode::ServerError(0),
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match response {
|
match response {
|
||||||
zebra_state::ReadResponse::Block(Some(block)) => match verbosity {
|
zebra_state::ReadResponse::Block(Some(block)) => {
|
||||||
0 => Ok(GetBlock::Raw(block.into())),
|
Ok(GetBlock::Raw(block.into()))
|
||||||
1 => Ok(GetBlock::Object {
|
}
|
||||||
tx: block
|
zebra_state::ReadResponse::Block(None) => Err(Error {
|
||||||
.transactions
|
code: MISSING_BLOCK_ERROR_CODE,
|
||||||
.iter()
|
message: "Block not found".to_string(),
|
||||||
.map(|tx| tx.hash().encode_hex())
|
|
||||||
.collect(),
|
|
||||||
}),
|
|
||||||
_ => Err(Error {
|
|
||||||
code: ErrorCode::InvalidParams,
|
|
||||||
message: "Invalid verbosity value".to_string(),
|
|
||||||
data: None,
|
data: None,
|
||||||
}),
|
}),
|
||||||
},
|
_ => unreachable!("unmatched response to a block request"),
|
||||||
zebra_state::ReadResponse::Block(None) => Err(Error {
|
}
|
||||||
code: MISSING_BLOCK_ERROR_CODE,
|
} else if verbosity == 1 {
|
||||||
message: "Block not found".to_string(),
|
let request = zebra_state::ReadRequest::TransactionIdsForBlock(height.into());
|
||||||
|
let response = state
|
||||||
|
.ready()
|
||||||
|
.and_then(|service| service.call(request))
|
||||||
|
.await
|
||||||
|
.map_err(|error| Error {
|
||||||
|
code: ErrorCode::ServerError(0),
|
||||||
|
message: error.to_string(),
|
||||||
|
data: None,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
match response {
|
||||||
|
zebra_state::ReadResponse::TransactionIdsForBlock(Some(tx_ids)) => {
|
||||||
|
let tx_ids = tx_ids.iter().map(|tx_id| tx_id.encode_hex()).collect();
|
||||||
|
Ok(GetBlock::Object { tx: tx_ids })
|
||||||
|
}
|
||||||
|
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"),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(Error {
|
||||||
|
code: ErrorCode::InvalidParams,
|
||||||
|
message: "Invalid verbosity value".to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
}),
|
})
|
||||||
_ => unreachable!("unmatched response to a block request"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
|
|
@ -1111,7 +1130,7 @@ pub enum GetBlock {
|
||||||
Raw(#[serde(with = "hex")] SerializedBlock),
|
Raw(#[serde(with = "hex")] SerializedBlock),
|
||||||
/// The block object.
|
/// The block object.
|
||||||
Object {
|
Object {
|
||||||
/// Vector of hex-encoded TXIDs of the transactions of the block
|
/// List of transaction IDs in block order, hex-encoded.
|
||||||
tx: Vec<String>,
|
tx: Vec<String>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -597,6 +597,18 @@ pub enum ReadRequest {
|
||||||
/// * [`ReadResponse::Transaction(None)`](ReadResponse::Transaction) otherwise.
|
/// * [`ReadResponse::Transaction(None)`](ReadResponse::Transaction) otherwise.
|
||||||
Transaction(transaction::Hash),
|
Transaction(transaction::Hash),
|
||||||
|
|
||||||
|
/// Looks up the transaction IDs for a block, using a block hash or height.
|
||||||
|
///
|
||||||
|
/// Returns
|
||||||
|
///
|
||||||
|
/// * An ordered list of transaction hashes, or
|
||||||
|
/// * `None` if the block was not found.
|
||||||
|
///
|
||||||
|
/// Note: Each block has at least one transaction: the coinbase transaction.
|
||||||
|
///
|
||||||
|
/// Returned txids are in the order they appear in the block.
|
||||||
|
TransactionIdsForBlock(HashOrHeight),
|
||||||
|
|
||||||
/// Looks up a UTXO identified by the given [`OutPoint`](transparent::OutPoint),
|
/// Looks up a UTXO identified by the given [`OutPoint`](transparent::OutPoint),
|
||||||
/// returning `None` immediately if it is unknown.
|
/// returning `None` immediately if it is unknown.
|
||||||
///
|
///
|
||||||
|
|
@ -728,6 +740,7 @@ impl ReadRequest {
|
||||||
ReadRequest::Depth(_) => "depth",
|
ReadRequest::Depth(_) => "depth",
|
||||||
ReadRequest::Block(_) => "block",
|
ReadRequest::Block(_) => "block",
|
||||||
ReadRequest::Transaction(_) => "transaction",
|
ReadRequest::Transaction(_) => "transaction",
|
||||||
|
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",
|
||||||
ReadRequest::BestChainUtxo { .. } => "best_chain_utxo",
|
ReadRequest::BestChainUtxo { .. } => "best_chain_utxo",
|
||||||
ReadRequest::AnyChainUtxo { .. } => "any_chain_utxo",
|
ReadRequest::AnyChainUtxo { .. } => "any_chain_utxo",
|
||||||
ReadRequest::BlockLocator => "block_locator",
|
ReadRequest::BlockLocator => "block_locator",
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ pub enum ReadResponse {
|
||||||
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
||||||
Transaction(Option<(Arc<Transaction>, block::Height)>),
|
Transaction(Option<(Arc<Transaction>, block::Height)>),
|
||||||
|
|
||||||
|
/// Response to [`ReadRequest::TransactionIdsForBlock`],
|
||||||
|
/// with an list of transaction hashes in block order,
|
||||||
|
/// or `None` if the block was not found.
|
||||||
|
TransactionIdsForBlock(Option<Arc<[transaction::Hash]>>),
|
||||||
|
|
||||||
/// Response to [`ReadRequest::BlockLocator`] with a block locator object.
|
/// Response to [`ReadRequest::BlockLocator`] with a block locator object.
|
||||||
BlockLocator(Vec<block::Hash>),
|
BlockLocator(Vec<block::Hash>),
|
||||||
|
|
||||||
|
|
@ -130,7 +135,8 @@ impl TryFrom<ReadResponse> for Response {
|
||||||
ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
|
ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
|
||||||
ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),
|
ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),
|
||||||
|
|
||||||
ReadResponse::BestChainUtxo(_)
|
ReadResponse::TransactionIdsForBlock(_)
|
||||||
|
| ReadResponse::BestChainUtxo(_)
|
||||||
| ReadResponse::SaplingTree(_)
|
| ReadResponse::SaplingTree(_)
|
||||||
| ReadResponse::OrchardTree(_)
|
| ReadResponse::OrchardTree(_)
|
||||||
| ReadResponse::AddressBalance(_)
|
| ReadResponse::AddressBalance(_)
|
||||||
|
|
|
||||||
|
|
@ -1173,7 +1173,7 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used by get_block RPC and the StateService.
|
// Used by the get_block (raw) RPC and the StateService.
|
||||||
ReadRequest::Block(hash_or_height) => {
|
ReadRequest::Block(hash_or_height) => {
|
||||||
let timer = CodeTimer::start();
|
let timer = CodeTimer::start();
|
||||||
|
|
||||||
|
|
@ -1227,6 +1227,39 @@ impl Service<ReadRequest> for ReadStateService {
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used by the getblock (verbose) RPC.
|
||||||
|
ReadRequest::TransactionIdsForBlock(hash_or_height) => {
|
||||||
|
let timer = CodeTimer::start();
|
||||||
|
|
||||||
|
let state = self.clone();
|
||||||
|
|
||||||
|
let span = Span::current();
|
||||||
|
tokio::task::spawn_blocking(move || {
|
||||||
|
span.in_scope(move || {
|
||||||
|
let transaction_ids = state.non_finalized_state_receiver.with_watch_data(
|
||||||
|
|non_finalized_state| {
|
||||||
|
read::transaction_hashes_for_block(
|
||||||
|
non_finalized_state.best_chain(),
|
||||||
|
&state.db,
|
||||||
|
hash_or_height,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// The work is done in the future.
|
||||||
|
timer.finish(
|
||||||
|
module_path!(),
|
||||||
|
line!(),
|
||||||
|
"ReadRequest::TransactionIdsForBlock",
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(ReadResponse::TransactionIdsForBlock(transaction_ids))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.map(|join_result| join_result.expect("panic in ReadRequest::Block"))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
// Currently unused.
|
// Currently unused.
|
||||||
ReadRequest::BestChainUtxo(outpoint) => {
|
ReadRequest::BestChainUtxo(outpoint) => {
|
||||||
let timer = CodeTimer::start();
|
let timer = CodeTimer::start();
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,39 @@ impl ZebraDb {
|
||||||
.map(|tx| (tx, transaction_location.height))
|
.map(|tx| (tx, transaction_location.height))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`transaction::Hash`]es in the block with `hash_or_height`,
|
||||||
|
/// if it exists in this chain.
|
||||||
|
///
|
||||||
|
/// Hashes are returned in block order.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the block is not found.
|
||||||
|
#[allow(clippy::unwrap_in_result)]
|
||||||
|
pub fn transaction_hashes_for_block(
|
||||||
|
&self,
|
||||||
|
hash_or_height: HashOrHeight,
|
||||||
|
) -> Option<Arc<[transaction::Hash]>> {
|
||||||
|
// Block
|
||||||
|
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
|
||||||
|
|
||||||
|
// Transaction hashes
|
||||||
|
let hash_by_tx_loc = self.db.cf_handle("hash_by_tx_loc").unwrap();
|
||||||
|
|
||||||
|
// Manually fetch the entire block's transaction hashes
|
||||||
|
let mut transaction_hashes = Vec::new();
|
||||||
|
|
||||||
|
for tx_index in 0..=Transaction::max_allocation() {
|
||||||
|
let tx_loc = TransactionLocation::from_u64(height, tx_index);
|
||||||
|
|
||||||
|
if let Some(tx_hash) = self.db.zs_get(&hash_by_tx_loc, &tx_loc) {
|
||||||
|
transaction_hashes.push(tx_hash);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(transaction_hashes.into())
|
||||||
|
}
|
||||||
|
|
||||||
// Write block methods
|
// Write block methods
|
||||||
|
|
||||||
/// Write `finalized` to the finalized state.
|
/// Write `finalized` to the finalized state.
|
||||||
|
|
|
||||||
|
|
@ -471,6 +471,21 @@ impl Chain {
|
||||||
.get(tx_loc.index.as_usize())
|
.get(tx_loc.index.as_usize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`transaction::Hash`]es in the block with `hash_or_height`,
|
||||||
|
/// if it exists in this chain.
|
||||||
|
///
|
||||||
|
/// Hashes are returned in block order.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the block is not found.
|
||||||
|
pub fn transaction_hashes_for_block(
|
||||||
|
&self,
|
||||||
|
hash_or_height: HashOrHeight,
|
||||||
|
) -> Option<Arc<[transaction::Hash]>> {
|
||||||
|
let transaction_hashes = self.block(hash_or_height)?.transaction_hashes.clone();
|
||||||
|
|
||||||
|
Some(transaction_hashes)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`block::Hash`] for `height`, if it exists in this chain.
|
/// Returns the [`block::Hash`] for `height`, if it exists in this chain.
|
||||||
pub fn hash_by_height(&self, height: Height) -> Option<block::Hash> {
|
pub fn hash_by_height(&self, height: Height) -> Option<block::Hash> {
|
||||||
let hash = self.blocks.get(&height)?.hash;
|
let hash = self.blocks.get(&height)?.hash;
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub use address::{
|
||||||
tx_id::transparent_tx_ids,
|
tx_id::transparent_tx_ids,
|
||||||
utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE},
|
utxo::{address_utxos, AddressUtxos, ADDRESS_HEIGHTS_FULL_RANGE},
|
||||||
};
|
};
|
||||||
pub use block::{any_utxo, block, block_header, transaction, utxo};
|
pub use block::{any_utxo, block, block_header, transaction, transaction_hashes_for_block, utxo};
|
||||||
pub use find::{
|
pub use find::{
|
||||||
block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
||||||
hash_by_height, height_by_hash, tip, tip_height,
|
hash_by_height, height_by_hash, tip, tip_height,
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,31 @@ where
|
||||||
.or_else(|| db.transaction(hash))
|
.or_else(|| db.transaction(hash))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`transaction::Hash`]es for the block with `hash_or_height`,
|
||||||
|
/// if it exists in the non-finalized `chain` or finalized `db`.
|
||||||
|
///
|
||||||
|
/// The returned hashes are in block order.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the block is not found.
|
||||||
|
pub fn transaction_hashes_for_block<C>(
|
||||||
|
chain: Option<C>,
|
||||||
|
db: &ZebraDb,
|
||||||
|
hash_or_height: HashOrHeight,
|
||||||
|
) -> Option<Arc<[transaction::Hash]>>
|
||||||
|
where
|
||||||
|
C: AsRef<Chain>,
|
||||||
|
{
|
||||||
|
// # Correctness
|
||||||
|
//
|
||||||
|
// Since blocks are the same in the finalized and non-finalized state, we
|
||||||
|
// check the most efficient alternative first. (`chain` is always in memory,
|
||||||
|
// but `db` stores blocks on disk, with a memory cache.)
|
||||||
|
chain
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|chain| chain.as_ref().transaction_hashes_for_block(hash_or_height))
|
||||||
|
.or_else(|| db.transaction_hashes_for_block(hash_or_height))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in the
|
/// Returns the [`Utxo`] for [`transparent::OutPoint`], if it exists in the
|
||||||
/// non-finalized `chain` or finalized `db`.
|
/// non-finalized `chain` or finalized `db`.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -89,10 +89,12 @@ echo "$@"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "Querying $ZEBRAD $ZEBRAD_NET chain at height >=$ZEBRAD_HEIGHT..."
|
echo "Querying $ZEBRAD $ZEBRAD_NET chain at height >=$ZEBRAD_HEIGHT..."
|
||||||
$ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" "$@" > "$ZEBRAD_RESPONSE"
|
time $ZCASH_CLI -rpcport="$ZEBRAD_RPC_PORT" "$@" > "$ZEBRAD_RESPONSE"
|
||||||
|
echo
|
||||||
|
|
||||||
echo "Querying $ZCASHD $ZCASHD_NET chain at height >=$ZCASHD_HEIGHT..."
|
echo "Querying $ZCASHD $ZCASHD_NET chain at height >=$ZCASHD_HEIGHT..."
|
||||||
$ZCASH_CLI "$@" > "$ZCASHD_RESPONSE"
|
time $ZCASH_CLI "$@" > "$ZCASHD_RESPONSE"
|
||||||
|
echo
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue