diff --git a/zebra-chain/src/block/serialize.rs b/zebra-chain/src/block/serialize.rs index d5b6b6e7b..3cf19ae60 100644 --- a/zebra-chain/src/block/serialize.rs +++ b/zebra-chain/src/block/serialize.rs @@ -162,3 +162,9 @@ impl AsRef<[u8]> for SerializedBlock { self.bytes.as_ref() } } + +impl From> for SerializedBlock { + fn from(bytes: Vec) -> Self { + Self { bytes } + } +} diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 9e5e73143..eed188db6 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -126,16 +126,18 @@ pub trait Rpc { /// # Parameters /// /// - `height`: (string, required) The height number for the block to be returned. + /// - `verbosity`: (numeric, optional, default=1) 0 for hex encoded data, 1 for a json object, + /// and 2 for json object with transaction data. /// /// # Notes /// - /// We only expose the `data` field as lightwalletd uses the non-verbose - /// mode for all getblock calls: + /// With verbosity=1, [`lightwalletd` only reads the `tx` field of the + /// result](https://github.com/zcash/lightwalletd/blob/dfac02093d85fb31fb9a8475b884dd6abca966c7/common/common.go#L152), + /// so we only return that for now. /// /// `lightwalletd` only requests blocks by height, so we don't support /// getting blocks by hash. (But we parse the height as a JSON string, not an integer). - /// - /// The `verbosity` parameter is ignored but required in the call. + /// `lightwalletd` also does not use verbosity=2, so we don't support it. #[rpc(name = "getblock")] fn get_block(&self, height: String, verbosity: u8) -> BoxFuture>; @@ -515,7 +517,7 @@ where .boxed() } - fn get_block(&self, height: String, _verbosity: u8) -> BoxFuture> { + fn get_block(&self, height: String, verbosity: u8) -> BoxFuture> { let mut state = self.state.clone(); async move { @@ -538,7 +540,21 @@ where })?; match response { - zebra_state::ReadResponse::Block(Some(block)) => Ok(GetBlock(block.into())), + 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(), + data: None, + }), + }, zebra_state::ReadResponse::Block(None) => Err(Error { code: MISSING_BLOCK_ERROR_CODE, message: "Block not found".to_string(), @@ -1070,7 +1086,16 @@ pub struct SentTransactionHash(#[serde(with = "hex")] transaction::Hash); /// /// See the notes for the [`Rpc::get_block` method]. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] -pub struct GetBlock(#[serde(with = "hex")] SerializedBlock); +#[serde(untagged)] +pub enum GetBlock { + /// The request block, hex-encoded. + Raw(#[serde(with = "hex")] SerializedBlock), + /// The block object. + Object { + /// Vector of hex-encoded TXIDs of the transactions of the block + tx: Vec, + }, +} /// Response to a `getbestblockhash` RPC request. /// diff --git a/zebra-rpc/src/methods/tests/snapshot.rs b/zebra-rpc/src/methods/tests/snapshot.rs index cfc766a3e..f5031e9c6 100644 --- a/zebra-rpc/src/methods/tests/snapshot.rs +++ b/zebra-rpc/src/methods/tests/snapshot.rs @@ -82,7 +82,7 @@ async fn test_rpc_response_data_for_network(network: Network) { .expect("We should have an AddressBalance struct"); snapshot_rpc_getaddressbalance(get_address_balance, &settings); - // `getblock` + // `getblock`, verbosity=0 const BLOCK_HEIGHT: u32 = 1; let get_block = rpc .get_block(BLOCK_HEIGHT.to_string(), 0u8) @@ -90,6 +90,13 @@ async fn test_rpc_response_data_for_network(network: Network) { .expect("We should have a GetBlock struct"); snapshot_rpc_getblock(get_block, block_data.get(&BLOCK_HEIGHT).unwrap(), &settings); + // `getblock`, verbosity=1 + let get_block = rpc + .get_block(BLOCK_HEIGHT.to_string(), 1u8) + .await + .expect("We should have a GetBlock struct"); + snapshot_rpc_getblock_verbose(get_block, &settings); + // `getbestblockhash` let get_best_block_hash = rpc .get_best_block_hash() @@ -211,6 +218,11 @@ fn snapshot_rpc_getblock(block: GetBlock, block_data: &[u8], settings: &insta::S }); } +/// Check `getblock` response with verbosity=1, using `cargo insta` and JSON serialization. +fn snapshot_rpc_getblock_verbose(block: GetBlock, settings: &insta::Settings) { + settings.bind(|| insta::assert_json_snapshot!("get_block_verbose", block)); +} + /// Snapshot `getbestblockhash` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_getbestblockhash(tip_hash: GetBestBlockHash, settings: &insta::Settings) { settings.bind(|| insta::assert_json_snapshot!("get_best_block_hash", tip_hash)); diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@mainnet_10.snap new file mode 100644 index 000000000..b9fc9794f --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@mainnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: block +--- +{ + "tx": [ + "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609" + ] +} diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@testnet_10.snap new file mode 100644 index 000000000..dbc2ce43d --- /dev/null +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose@testnet_10.snap @@ -0,0 +1,9 @@ +--- +source: zebra-rpc/src/methods/tests/snapshot.rs +expression: block +--- +{ + "tx": [ + "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75" + ] +} diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index f82f07a9a..5fe58f173 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -77,14 +77,33 @@ async fn rpc_getblock() { Mainnet, ); - // Make calls and check response - for (i, block) in blocks.into_iter().enumerate() { + // Make calls with verbosity=0 and check response + for (i, block) in blocks.iter().enumerate() { let get_block = rpc .get_block(i.to_string(), 0u8) .await .expect("We should have a GetBlock struct"); - assert_eq!(get_block.0, block.into()); + assert_eq!(get_block, GetBlock::Raw(block.clone().into())); + } + + // Make calls with verbosity=1 and check response + for (i, block) in blocks.iter().enumerate() { + let get_block = rpc + .get_block(i.to_string(), 1u8) + .await + .expect("We should have a GetBlock struct"); + + assert_eq!( + get_block, + GetBlock::Object { + tx: block + .transactions + .iter() + .map(|tx| tx.hash().encode_hex()) + .collect() + } + ); } mempool.expect_no_requests().await; diff --git a/zebra-rpc/src/tests/vectors.rs b/zebra-rpc/src/tests/vectors.rs index d6aab8506..1779334d9 100644 --- a/zebra-rpc/src/tests/vectors.rs +++ b/zebra-rpc/src/tests/vectors.rs @@ -1,4 +1,4 @@ -use crate::methods::GetRawTransaction; +use crate::methods::{GetBlock, GetRawTransaction}; #[test] pub fn test_transaction_serialization() { @@ -17,3 +17,20 @@ pub fn test_transaction_serialization() { assert_eq!(j, expected_json); } + +#[test] +pub fn test_block_serialization() { + let expected_tx = GetBlock::Raw(vec![0x42].into()); + let expected_json = r#""42""#; + let j = serde_json::to_string(&expected_tx).unwrap(); + + assert_eq!(j, expected_json); + + let expected_tx = GetBlock::Object { + tx: vec!["42".into()], + }; + let expected_json = r#"{"tx":["42"]}"#; + let j = serde_json::to_string(&expected_tx).unwrap(); + + assert_eq!(j, expected_json); +}