Skip to content

Commit

Permalink
Removes handling for verbosity = 3 in getblock method, adds finalorch…
Browse files Browse the repository at this point in the history
…ardroot field, removes unnecessary state request. (#9008)
  • Loading branch information
arya2 authored Nov 12, 2024
1 parent 618beb3 commit 349c682
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 203 deletions.
265 changes: 62 additions & 203 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,179 +780,50 @@ where
}),
_ => unreachable!("unmatched response to a block request"),
}
} else if verbosity == 1 || verbosity == 2 {
let r: Result<GetBlockHeader> = self_clone
} else if matches!(verbosity, 1 | 2) {
let get_block_header_result: Result<GetBlockHeader> = self_clone
.get_block_header(original_hash_or_height, Some(true))
.await;

let GetBlockHeader::Object(h) = r? else {
let GetBlockHeader::Object(block_header) = get_block_header_result? else {
panic!("must return Object")
};
let hash = h.hash.0;

// # Concurrency
//
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
let requests = vec![
// Get transaction IDs from the transaction index by block hash
//
// # Concurrency
//
// 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()),
];

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"),
};

let sapling = SaplingTrees {
size: sapling_note_commitment_tree_count,
};

let orchard = OrchardTrees {
size: orchard_note_commitment_tree_count,
};

let trees = GetBlockTrees { sapling, orchard };

Ok(GetBlock::Object {
hash: h.hash,
confirmations: h.confirmations,
height: Some(h.height),
version: Some(h.version),
merkle_root: Some(h.merkle_root),
time: Some(h.time),
nonce: Some(h.nonce),
solution: Some(h.solution),
bits: Some(h.bits),
difficulty: Some(h.difficulty),
// TODO
tx,
trees,
// TODO
size: None,
final_sapling_root: Some(h.final_sapling_root),
// TODO
final_orchard_root: None,
previous_block_hash: Some(h.previous_block_hash),
next_block_hash: h.next_block_hash,
})
} else if verbosity == 3 {
// # Performance
//
// This RPC is used in `lightwalletd`'s initial sync of 2 million blocks,
// so it needs to load all its fields very efficiently.
//
// Currently, we get the block hash and transaction IDs from indexes,
// which is much more efficient than loading all the block data,
// then hashing the block header and all the transactions.

// Get the block hash from the height -> hash index, if needed
//
// # Concurrency
//
// For consistency, this lookup must be performed first, then all the other
// lookups must be based on the hash.
//
// All possible responses are valid, even if the best chain changes. Clients
// must be able to handle chain forks, including a hash for a block that is
// later discovered to be on a side chain.

let should_read_block_header = verbosity == 2;

let hash = match hash_or_height {
HashOrHeight::Hash(hash) => hash,
HashOrHeight::Height(height) => {
let request = zebra_state::ReadRequest::BestChainBlockHash(height);
let response = state
.ready()
.and_then(|service| service.call(request))
.await
.map_server_error()?;

match response {
zebra_state::ReadResponse::BlockHash(Some(hash)) => hash,
zebra_state::ReadResponse::BlockHash(None) => {
return Err(Error {
code: MISSING_BLOCK_ERROR_CODE,
message: "block height not in best chain".to_string(),
data: None,
})
}
_ => unreachable!("unmatched response to a block hash request"),
}
}
};
let GetBlockHeaderObject {
hash,
confirmations,
height,
version,
merkle_root,
final_sapling_root,
sapling_tree_size,
time,
nonce,
solution,
bits,
difficulty,
previous_block_hash,
next_block_hash,
} = *block_header;

// # Concurrency
//
// We look up by block hash so the hash, transaction IDs, and confirmations
// are consistent.
let mut requests = vec![
let hash_or_height = hash.0.into();
let requests = vec![
// Get transaction IDs from the transaction index by block hash
//
// # Concurrency
//
// 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()),
zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
// 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),
zebra_state::ReadRequest::OrchardTree(hash_or_height),
];

if should_read_block_header {
// Block header
requests.push(zebra_state::ReadRequest::BlockHeader(hash.into()))
}

let mut futs = FuturesOrdered::new();

for request in requests {
Expand All @@ -969,76 +840,56 @@ where
_ => 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>
const NOT_IN_BEST_CHAIN_CONFIRMATIONS: i64 = -1;

let depth_response = futs.next().await.expect("`futs` should not be empty");
let confirmations = match depth_response.map_server_error()? {
// Confirmations are one more than the depth.
// 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(None) => NOT_IN_BEST_CHAIN_CONFIRMATIONS,
_ => unreachable!("unmatched response to a depth request"),
let zebra_state::ReadResponse::OrchardTree(orchard_tree) =
orchard_tree_response.map_server_error()?
else {
unreachable!("unmatched response to a OrchardTree request");
};

let (time, height) = if should_read_block_header {
let block_header_response =
futs.next().await.expect("`futs` should not be empty");
// This could be `None` if there's a chain reorg between state queries.
let orchard_tree =
orchard_tree.ok_or_server_error("missing sapling tree for block")?;

match block_header_response.map_server_error()? {
zebra_state::ReadResponse::BlockHeader { header, height, .. } => {
(Some(header.time.timestamp()), Some(height))
}
_ => unreachable!("unmatched response to a BlockHeader request"),
}
let orchard_tree_size = orchard_tree.count();
let final_orchard_root: [u8; 32] = if orchard_tree_size != 0 {
let mut root: [u8; 32] = orchard_tree.root().into();
root.reverse();
root
} else {
(None, hash_or_height.height())
[0; 32]
};

let sapling = SaplingTrees {
size: sapling_note_commitment_tree_count,
size: sapling_tree_size,
};

let orchard = OrchardTrees {
size: orchard_note_commitment_tree_count,
size: orchard_tree_size,
};

let trees = GetBlockTrees { sapling, orchard };

Ok(GetBlock::Object {
hash: GetBlockHash(hash),
hash,
confirmations,
height,
time,
height: Some(height),
version: Some(version),
merkle_root: Some(merkle_root),
time: Some(time),
nonce: Some(nonce),
solution: Some(solution),
bits: Some(bits),
difficulty: Some(difficulty),
// TODO
tx,
trees,
// TODO
size: None,
version: None,
merkle_root: None,
final_sapling_root: None,
final_orchard_root: None,
nonce: None,
bits: None,
difficulty: None,
previous_block_hash: None,
next_block_hash: None,
solution: None,
final_sapling_root: Some(final_sapling_root),
final_orchard_root: Some(final_orchard_root),
previous_block_hash: Some(previous_block_hash),
next_block_hash,
})
} else {
Err(Error {
Expand Down Expand Up @@ -1125,7 +976,8 @@ where
let mut nonce = *header.nonce;
nonce.reverse();

let final_sapling_root: [u8; 32] = if sapling_tree.position().is_some() {
let sapling_tree_size = sapling_tree.count();
let final_sapling_root: [u8; 32] = if sapling_tree_size != 0 {
let mut root: [u8; 32] = sapling_tree.root().into();
root.reverse();
root
Expand All @@ -1142,6 +994,7 @@ where
version: header.version,
merkle_root: header.merkle_root,
final_sapling_root,
sapling_tree_size,
time: header.time.timestamp(),
nonce,
solution: header.solution,
Expand Down Expand Up @@ -1977,6 +1830,11 @@ pub struct GetBlockHeaderObject {
#[serde(with = "hex", rename = "finalsaplingroot")]
pub final_sapling_root: [u8; 32],

/// The number of Sapling notes in the Sapling note commitment tree
/// after applying this block. Used by the `getblock` RPC method.
#[serde(skip)]
pub sapling_tree_size: u64,

/// The block time of the requested block header in non-leap seconds since Jan 1 1970 GMT.
pub time: i64,

Expand Down Expand Up @@ -2022,6 +1880,7 @@ impl Default for GetBlockHeaderObject {
version: 4,
merkle_root: block::merkle::Root([0; 32]),
final_sapling_root: Default::default(),
sapling_tree_size: Default::default(),
time: 0,
nonce: [0; 32],
solution: Solution::for_proposal(),
Expand Down
1 change: 1 addition & 0 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,7 @@ async fn rpc_getblockheader() {
version: 4,
merkle_root: block.header.merkle_root,
final_sapling_root: expected_final_sapling_root,
sapling_tree_size: sapling_tree.count(),
time: block.header.time.timestamp(),
nonce: expected_nonce,
solution: block.header.solution,
Expand Down

0 comments on commit 349c682

Please sign in to comment.