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();
|
||||
|
||||
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),
|
||||
message: error.to_string(),
|
||||
data: None,
|
||||
})?;
|
||||
|
||||
let request =
|
||||
zebra_state::ReadRequest::Block(zebra_state::HashOrHeight::Height(height));
|
||||
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,
|
||||
})?;
|
||||
if verbosity == 0 {
|
||||
let request = zebra_state::ReadRequest::Block(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::Block(Some(block)) => match verbosity {
|
||||
0 => Ok(GetBlock::Raw(block.into())),
|
||||
1 => Ok(GetBlock::Object {
|
||||
tx: block
|
||||
.transactions
|
||||
.iter()
|
||||
.map(|tx| tx.hash().encode_hex())
|
||||
.collect(),
|
||||
}),
|
||||
_ => Err(Error {
|
||||
code: ErrorCode::InvalidParams,
|
||||
message: "Invalid verbosity value".to_string(),
|
||||
match response {
|
||||
zebra_state::ReadResponse::Block(Some(block)) => {
|
||||
Ok(GetBlock::Raw(block.into()))
|
||||
}
|
||||
zebra_state::ReadResponse::Block(None) => Err(Error {
|
||||
code: MISSING_BLOCK_ERROR_CODE,
|
||||
message: "Block not found".to_string(),
|
||||
data: None,
|
||||
}),
|
||||
},
|
||||
zebra_state::ReadResponse::Block(None) => Err(Error {
|
||||
code: MISSING_BLOCK_ERROR_CODE,
|
||||
message: "Block not found".to_string(),
|
||||
_ => unreachable!("unmatched response to a block request"),
|
||||
}
|
||||
} else if verbosity == 1 {
|
||||
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,
|
||||
}),
|
||||
_ => unreachable!("unmatched response to a block request"),
|
||||
})
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
|
@ -1111,7 +1130,7 @@ pub enum GetBlock {
|
|||
Raw(#[serde(with = "hex")] SerializedBlock),
|
||||
/// The block 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>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -597,6 +597,18 @@ pub enum ReadRequest {
|
|||
/// * [`ReadResponse::Transaction(None)`](ReadResponse::Transaction) otherwise.
|
||||
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),
|
||||
/// returning `None` immediately if it is unknown.
|
||||
///
|
||||
|
@ -728,6 +740,7 @@ impl ReadRequest {
|
|||
ReadRequest::Depth(_) => "depth",
|
||||
ReadRequest::Block(_) => "block",
|
||||
ReadRequest::Transaction(_) => "transaction",
|
||||
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",
|
||||
ReadRequest::BestChainUtxo { .. } => "best_chain_utxo",
|
||||
ReadRequest::AnyChainUtxo { .. } => "any_chain_utxo",
|
||||
ReadRequest::BlockLocator => "block_locator",
|
||||
|
|
|
@ -67,6 +67,11 @@ pub enum ReadResponse {
|
|||
/// Response to [`ReadRequest::Transaction`] with the specified transaction.
|
||||
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.
|
||||
BlockLocator(Vec<block::Hash>),
|
||||
|
||||
|
@ -130,7 +135,8 @@ impl TryFrom<ReadResponse> for Response {
|
|||
ReadResponse::BlockHashes(hashes) => Ok(Response::BlockHashes(hashes)),
|
||||
ReadResponse::BlockHeaders(headers) => Ok(Response::BlockHeaders(headers)),
|
||||
|
||||
ReadResponse::BestChainUtxo(_)
|
||||
ReadResponse::TransactionIdsForBlock(_)
|
||||
| ReadResponse::BestChainUtxo(_)
|
||||
| ReadResponse::SaplingTree(_)
|
||||
| ReadResponse::OrchardTree(_)
|
||||
| ReadResponse::AddressBalance(_)
|
||||
|
|
|
@ -1173,7 +1173,7 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
.boxed()
|
||||
}
|
||||
|
||||
// Used by get_block RPC and the StateService.
|
||||
// Used by the get_block (raw) RPC and the StateService.
|
||||
ReadRequest::Block(hash_or_height) => {
|
||||
let timer = CodeTimer::start();
|
||||
|
||||
|
@ -1227,6 +1227,39 @@ impl Service<ReadRequest> for ReadStateService {
|
|||
.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.
|
||||
ReadRequest::BestChainUtxo(outpoint) => {
|
||||
let timer = CodeTimer::start();
|
||||
|
|
|
@ -226,6 +226,39 @@ impl ZebraDb {
|
|||
.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 `finalized` to the finalized state.
|
||||
|
|
|
@ -471,6 +471,21 @@ impl Chain {
|
|||
.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.
|
||||
pub fn hash_by_height(&self, height: Height) -> Option<block::Hash> {
|
||||
let hash = self.blocks.get(&height)?.hash;
|
||||
|
|
|
@ -27,7 +27,7 @@ pub use address::{
|
|||
tx_id::transparent_tx_ids,
|
||||
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::{
|
||||
block_locator, chain_contains_hash, depth, find_chain_hashes, find_chain_headers,
|
||||
hash_by_height, height_by_hash, tip, tip_height,
|
||||
|
|
|
@ -93,6 +93,31 @@ where
|
|||
.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
|
||||
/// non-finalized `chain` or finalized `db`.
|
||||
///
|
||||
|
|
|
@ -89,10 +89,12 @@ echo "$@"
|
|||
echo
|
||||
|
||||
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..."
|
||||
$ZCASH_CLI "$@" > "$ZCASHD_RESPONSE"
|
||||
time $ZCASH_CLI "$@" > "$ZCASHD_RESPONSE"
|
||||
echo
|
||||
|
||||
echo
|
||||
|
||||
|
|
Loading…
Reference in New Issue