Skip to content

Commit 83d1279

Browse files
committed
feat(rpc): fill size field in getblock with verbosity=2
1 parent f873aa1 commit 83d1279

13 files changed

+202
-35
lines changed

zebra-rpc/src/methods.rs

+31-30
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,7 @@ pub trait Rpc {
172172
///
173173
/// # Notes
174174
///
175-
/// Zebra previously partially supported verbosity=1 by returning only the
176-
/// fields required by lightwalletd ([`lightwalletd` only reads the `tx`
177-
/// field of the result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152)).
178-
/// That verbosity level was migrated to "3"; so while lightwalletd will
179-
/// still work by using verbosity=1, it will sync faster if it is changed to
180-
/// use verbosity=3.
175+
/// The `size` field is only returned with verbosity=2.
181176
///
182177
/// The undocumented `chainwork` field is not returned.
183178
#[method(name = "getblock")]
@@ -887,7 +882,7 @@ where
887882

888883
let transactions_request = match verbosity {
889884
1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height),
890-
2 => zebra_state::ReadRequest::Block(hash_or_height),
885+
2 => zebra_state::ReadRequest::BlockAndSize(hash_or_height),
891886
_other => panic!("get_block_header_fut should be none"),
892887
};
893888

@@ -916,28 +911,34 @@ where
916911
}
917912

918913
let tx_ids_response = futs.next().await.expect("`futs` should not be empty");
919-
let tx: Vec<_> = match tx_ids_response.map_misc_error()? {
920-
zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => tx_ids
921-
.ok_or_misc_error("block not found")?
922-
.iter()
923-
.map(|tx_id| GetBlockTransaction::Hash(*tx_id))
924-
.collect(),
925-
zebra_state::ReadResponse::Block(block) => block
926-
.ok_or_misc_error("Block not found")?
927-
.transactions
928-
.iter()
929-
.map(|tx| {
930-
GetBlockTransaction::Object(TransactionObject::from_transaction(
931-
tx.clone(),
932-
Some(height),
933-
Some(
934-
confirmations
935-
.try_into()
936-
.expect("should be less than max block height, i32::MAX"),
937-
),
938-
))
939-
})
940-
.collect(),
914+
let (tx, size): (Vec<_>, Option<usize>) = match tx_ids_response.map_misc_error()? {
915+
zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => (
916+
tx_ids
917+
.ok_or_misc_error("block not found")?
918+
.iter()
919+
.map(|tx_id| GetBlockTransaction::Hash(*tx_id))
920+
.collect(),
921+
None,
922+
),
923+
zebra_state::ReadResponse::BlockAndSize(block_and_size) => {
924+
let (block, size) = block_and_size.ok_or_misc_error("Block not found")?;
925+
let transactions = block
926+
.transactions
927+
.iter()
928+
.map(|tx| {
929+
GetBlockTransaction::Object(TransactionObject::from_transaction(
930+
tx.clone(),
931+
Some(height),
932+
Some(
933+
confirmations
934+
.try_into()
935+
.expect("should be less than max block height, i32::MAX"),
936+
),
937+
))
938+
})
939+
.collect();
940+
(transactions, Some(size))
941+
}
941942
_ => unreachable!("unmatched response to a transaction_ids_for_block request"),
942943
};
943944

@@ -984,7 +985,7 @@ where
984985
difficulty: Some(difficulty),
985986
tx,
986987
trees,
987-
size: None,
988+
size: size.map(|size| size as i64),
988989
block_commitments: Some(block_commitments),
989990
final_sapling_root: Some(final_sapling_root),
990991
final_orchard_root,

zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@mainnet_10.snap

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ expression: block
55
{
66
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
77
"confirmations": 10,
8+
"size": 1617,
89
"height": 1,
910
"version": 4,
1011
"merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",

zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@testnet_10.snap

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ expression: block
55
{
66
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
77
"confirmations": 10,
8+
"size": 1618,
89
"height": 1,
910
"version": 4,
1011
"merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",

zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@mainnet_10.snap

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ expression: block
55
{
66
"hash": "0007bc227e1c57a4a70e237cad00e7b7ce565155ab49166bc57397a26d339283",
77
"confirmations": 10,
8+
"size": 1617,
89
"height": 1,
910
"version": 4,
1011
"merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",

zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@testnet_10.snap

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ expression: block
55
{
66
"hash": "025579869bcf52a989337342f5f57a84f3a28b968f7d6a8307902b065a668d23",
77
"confirmations": 10,
8+
"size": 1618,
89
"height": 1,
910
"version": 4,
1011
"merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",

zebra-rpc/src/methods/tests/vectors.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ async fn rpc_getblock() {
316316
}))
317317
.collect(),
318318
trees,
319-
size: None,
319+
size: Some(block.zcash_serialize_to_vec().unwrap().len() as i64),
320320
version: Some(block.header.version),
321321
merkle_root: Some(block.header.merkle_root),
322322
block_commitments: Some(expected_block_commitments),
@@ -364,7 +364,7 @@ async fn rpc_getblock() {
364364
}))
365365
.collect(),
366366
trees,
367-
size: None,
367+
size: Some(block.zcash_serialize_to_vec().unwrap().len() as i64),
368368
version: Some(block.header.version),
369369
merkle_root: Some(block.header.merkle_root),
370370
block_commitments: Some(expected_block_commitments),

zebra-state/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ pub use request::Spend;
5151
pub use response::{GetBlockTemplateChainInfo, KnownBlock, MinedTx, ReadResponse, Response};
5252
pub use service::{
5353
chain_tip::{ChainTipBlock, ChainTipChange, ChainTipSender, LatestChainTip, TipAction},
54-
check, init, init_read_only,
54+
check,
55+
finalized_state::FinalizedState,
56+
init, init_read_only,
5557
non_finalized_state::NonFinalizedState,
5658
spawn_init, spawn_init_read_only,
5759
watch_receiver::WatchReceiver,

zebra-state/src/request.rs

+9
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,14 @@ pub enum ReadRequest {
897897
/// [`block::Height`] using `.into()`.
898898
Block(HashOrHeight),
899899

900+
//// Same as Block, but also returns serialized block size.
901+
////
902+
/// Returns
903+
///
904+
/// * [`ReadResponse::BlockAndSize(Some((Arc<Block>, usize)))`](ReadResponse::BlockAndSize) if the block is in the best chain;
905+
/// * [`ReadResponse::BlockAndSize(None)`](ReadResponse::BlockAndSize) otherwise.
906+
BlockAndSize(HashOrHeight),
907+
900908
/// Looks up a block header by hash or height in the current best chain.
901909
///
902910
/// Returns
@@ -1143,6 +1151,7 @@ impl ReadRequest {
11431151
ReadRequest::TipPoolValues => "tip_pool_values",
11441152
ReadRequest::Depth(_) => "depth",
11451153
ReadRequest::Block(_) => "block",
1154+
ReadRequest::BlockAndSize(_) => "block_and_size",
11461155
ReadRequest::BlockHeader(_) => "block_header",
11471156
ReadRequest::Transaction(_) => "transaction",
11481157
ReadRequest::TransactionIdsForBlock(_) => "transaction_ids_for_block",

zebra-state/src/response.rs

+8
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ pub enum Response {
5050
/// Response to [`Request::Block`] with the specified block.
5151
Block(Option<Arc<Block>>),
5252

53+
/// Response to [`Request::BlockAndSize`] with the specified block and size.
54+
BlockAndSize(Option<(Arc<Block>, usize)>),
55+
5356
/// The response to a `BlockHeader` request.
5457
BlockHeader {
5558
/// The header of the requested block
@@ -157,6 +160,10 @@ pub enum ReadResponse {
157160
/// Response to [`ReadRequest::Block`] with the specified block.
158161
Block(Option<Arc<Block>>),
159162

163+
/// Response to [`ReadRequest::BlockAndSize`] with the specified block and
164+
/// serialized size.
165+
BlockAndSize(Option<(Arc<Block>, usize)>),
166+
160167
/// The response to a `BlockHeader` request.
161168
BlockHeader {
162169
/// The header of the requested block
@@ -311,6 +318,7 @@ impl TryFrom<ReadResponse> for Response {
311318
ReadResponse::BlockHash(hash) => Ok(Response::BlockHash(hash)),
312319

313320
ReadResponse::Block(block) => Ok(Response::Block(block)),
321+
ReadResponse::BlockAndSize(block) => Ok(Response::BlockAndSize(block)),
314322
ReadResponse::BlockHeader {
315323
header,
316324
hash,

zebra-state/src/service.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,31 @@ impl Service<ReadRequest> for ReadStateService {
13111311
.wait_for_panics()
13121312
}
13131313

1314+
// Used by the get_block (raw) RPC and the StateService.
1315+
ReadRequest::BlockAndSize(hash_or_height) => {
1316+
let state = self.clone();
1317+
1318+
tokio::task::spawn_blocking(move || {
1319+
span.in_scope(move || {
1320+
let block_and_size = state.non_finalized_state_receiver.with_watch_data(
1321+
|non_finalized_state| {
1322+
read::block_and_size(
1323+
non_finalized_state.best_chain(),
1324+
&state.db,
1325+
hash_or_height,
1326+
)
1327+
},
1328+
);
1329+
1330+
// The work is done in the future.
1331+
timer.finish(module_path!(), line!(), "ReadRequest::BlockAndSize");
1332+
1333+
Ok(ReadResponse::BlockAndSize(block_and_size))
1334+
})
1335+
})
1336+
.wait_for_panics()
1337+
}
1338+
13141339
// Used by the get_block (verbose) RPC and the StateService.
13151340
ReadRequest::BlockHeader(hash_or_height) => {
13161341
let state = self.clone();

zebra-state/src/service/finalized_state/zebra_db/block.rs

+92-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use zebra_chain::{
2424
parallel::tree::NoteCommitmentTrees,
2525
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
2626
sapling,
27-
serialization::TrustedPreallocate,
27+
serialization::{CompactSizeMessage, TrustedPreallocate, ZcashSerialize as _},
2828
transaction::{self, Transaction},
2929
transparent,
3030
value_balance::ValueBalance,
@@ -39,6 +39,7 @@ use crate::{
3939
transparent::{AddressBalanceLocation, OutputLocation},
4040
},
4141
zebra_db::{metrics::block_precommit_metrics, ZebraDb},
42+
FromDisk, RawBytes,
4243
},
4344
BoxError, HashOrHeight,
4445
};
@@ -132,6 +133,19 @@ impl ZebraDb {
132133
Some(header)
133134
}
134135

136+
/// Returns the raw [`block::Header`] with [`block::Hash`] or [`Height`], if
137+
/// it exists in the finalized chain.
138+
#[allow(clippy::unwrap_in_result)]
139+
fn raw_block_header(&self, hash_or_height: HashOrHeight) -> Option<RawBytes> {
140+
// Block Header
141+
let block_header_by_height = self.db.cf_handle("block_header_by_height").unwrap();
142+
143+
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
144+
let header: RawBytes = self.db.zs_get(&block_header_by_height, &height)?;
145+
146+
Some(header)
147+
}
148+
135149
/// Returns the [`Block`] with [`block::Hash`] or
136150
/// [`Height`], if it exists in the finalized chain.
137151
//
@@ -161,6 +175,56 @@ impl ZebraDb {
161175
}))
162176
}
163177

178+
/// Returns the [`Block`] with [`block::Hash`] or [`Height`], if it exists
179+
/// in the finalized chain, and its serialized size.
180+
#[allow(clippy::unwrap_in_result)]
181+
pub fn block_and_size(&self, hash_or_height: HashOrHeight) -> Option<(Arc<Block>, usize)> {
182+
let (raw_header, raw_txs) = self.raw_block(hash_or_height)?;
183+
184+
let header = Arc::<block::Header>::from_bytes(raw_header.raw_bytes());
185+
let txs: Vec<_> = raw_txs
186+
.iter()
187+
.map(|raw_tx| Arc::<Transaction>::from_bytes(raw_tx.raw_bytes()))
188+
.collect();
189+
190+
// Compute the size of the block from the size of header and size of
191+
// transactions. This requires summing them all and also adding the
192+
// size of the CompactSize-encoded transaction count.
193+
// See https://developer.bitcoin.org/reference/block_chain.html#serialized-blocks
194+
let tx_count = CompactSizeMessage::try_from(txs.len()).unwrap();
195+
let tx_raw = tx_count.zcash_serialize_to_vec().unwrap();
196+
let size = raw_header.raw_bytes().len()
197+
+ raw_txs
198+
.iter()
199+
.map(|raw_tx| raw_tx.raw_bytes().len())
200+
.sum::<usize>()
201+
+ tx_raw.len();
202+
203+
let block = Block {
204+
header,
205+
transactions: txs,
206+
};
207+
Some((Arc::new(block), size))
208+
}
209+
210+
/// Returns the raw [`Block`] with [`block::Hash`] or
211+
/// [`Height`], if it exists in the finalized chain.
212+
#[allow(clippy::unwrap_in_result)]
213+
fn raw_block(&self, hash_or_height: HashOrHeight) -> Option<(RawBytes, Vec<RawBytes>)> {
214+
// Block
215+
let height = hash_or_height.height_or_else(|hash| self.height(hash))?;
216+
let header = self.raw_block_header(height.into())?;
217+
218+
// Transactions
219+
220+
let transactions = self
221+
.raw_transactions_by_height(height)
222+
.map(|(_, tx)| tx)
223+
.collect();
224+
225+
Some((header, transactions))
226+
}
227+
164228
/// Returns the Sapling [`note commitment tree`](sapling::tree::NoteCommitmentTree) specified by
165229
/// a hash or height, if it exists in the finalized state.
166230
#[allow(clippy::unwrap_in_result)]
@@ -233,6 +297,19 @@ impl ZebraDb {
233297
)
234298
}
235299

300+
/// Returns an iterator of all raw [`Transaction`]s for a provided block
301+
/// height in finalized state.
302+
#[allow(clippy::unwrap_in_result)]
303+
fn raw_transactions_by_height(
304+
&self,
305+
height: Height,
306+
) -> impl Iterator<Item = (TransactionLocation, RawBytes)> + '_ {
307+
self.raw_transactions_by_location_range(
308+
TransactionLocation::min_for_height(height)
309+
..=TransactionLocation::max_for_height(height),
310+
)
311+
}
312+
236313
/// Returns an iterator of all [`Transaction`]s in the provided range
237314
/// of [`TransactionLocation`]s in finalized state.
238315
#[allow(clippy::unwrap_in_result)]
@@ -247,6 +324,20 @@ impl ZebraDb {
247324
self.db.zs_forward_range_iter(tx_by_loc, range)
248325
}
249326

327+
/// Returns an iterator of all raw [`Transaction`]s in the provided range
328+
/// of [`TransactionLocation`]s in finalized state.
329+
#[allow(clippy::unwrap_in_result)]
330+
fn raw_transactions_by_location_range<R>(
331+
&self,
332+
range: R,
333+
) -> impl Iterator<Item = (TransactionLocation, RawBytes)> + '_
334+
where
335+
R: RangeBounds<TransactionLocation>,
336+
{
337+
let tx_by_loc = self.db.cf_handle("tx_by_loc").unwrap();
338+
self.db.zs_forward_range_iter(tx_by_loc, range)
339+
}
340+
250341
/// Returns the [`TransactionLocation`] for [`transaction::Hash`],
251342
/// if it exists in the finalized chain.
252343
#[allow(clippy::unwrap_in_result)]

zebra-state/src/service/read.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ pub use address::{
2929
utxo::{address_utxos, AddressUtxos},
3030
};
3131
pub use block::{
32-
any_utxo, block, block_header, mined_transaction, transaction_hashes_for_block, unspent_utxo,
32+
any_utxo, block, block_and_size, block_header, mined_transaction, transaction_hashes_for_block,
33+
unspent_utxo,
3334
};
3435

3536
#[cfg(feature = "indexer")]

0 commit comments

Comments
 (0)