Skip to content

Commit

Permalink
Use transaction hash index for verbose block requests, rather than bl…
Browse files Browse the repository at this point in the history
…ock data
  • Loading branch information
teor2345 committed Sep 29, 2022
1 parent 09a38c5 commit 9f55229
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 34 deletions.
81 changes: 50 additions & 31 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
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,
}),
_ => Err(Error {
code: ErrorCode::InvalidParams,
message: "Invalid verbosity value".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,
}),
},
zebra_state::ReadResponse::Block(None) => Err(Error {
code: MISSING_BLOCK_ERROR_CODE,
message: "Block not found".to_string(),
_ => 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()
Expand Down Expand Up @@ -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>,
},
}
Expand Down
13 changes: 13 additions & 0 deletions zebra-state/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion zebra-state/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>),

Expand Down Expand Up @@ -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(_)
Expand Down
35 changes: 34 additions & 1 deletion zebra-state/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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();
Expand Down
33 changes: 33 additions & 0 deletions zebra-state/src/service/finalized_state/zebra_db/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 15 additions & 0 deletions zebra-state/src/service/non_finalized_state/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion zebra-state/src/service/read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
25 changes: 25 additions & 0 deletions zebra-state/src/service/read/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
///
Expand Down

0 comments on commit 9f55229

Please sign in to comment.