From b290ce72dbaa2cdf85c8c5aa03d6342eed3f740e Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Mon, 18 Nov 2024 22:38:12 -0300 Subject: [PATCH 1/5] getblock: return tx objects with verbosity=2 --- zebra-rpc/src/methods.rs | 82 +++++++++++++++++++------- zebra-rpc/src/methods/tests/vectors.rs | 16 ++--- zebra-rpc/src/tests/vectors.rs | 6 +- 3 files changed, 72 insertions(+), 32 deletions(-) diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 52f28d606b3..4388d72f40a 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -760,6 +760,7 @@ where None }; + let self_clone = self.clone(); async move { let hash_or_height: HashOrHeight = hash_or_height.parse().map_server_error()?; @@ -839,11 +840,30 @@ where zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => tx_ids .ok_or_server_error("Block not found")? .iter() - .map(|tx_id| tx_id.encode_hex()) + .map(|tx_id| GetBlockTransaction::Hash(*tx_id)) .collect(), _ => unreachable!("unmatched response to a transaction_ids_for_block request"), }; + let tx = if verbosity >= 2 { + let mut tx_obj_vec = vec![]; + for txid in tx { + let GetBlockTransaction::Hash(txid) = txid else { + unreachable!("must be a Hash") + }; + let get_tx_result: Result = self_clone + .get_raw_transaction(txid.encode_hex(), Some(1)) + .await; + let GetRawTransaction::Object(tx_obj) = get_tx_result? else { + unreachable!("must return Object"); + }; + tx_obj_vec.push(GetBlockTransaction::Object(tx_obj)); + } + tx_obj_vec + } else { + tx + }; + let orchard_tree_response = futs.next().await.expect("`futs` should not be empty"); let zebra_state::ReadResponse::OrchardTree(orchard_tree) = orchard_tree_response.map_server_error()? @@ -1771,11 +1791,9 @@ pub enum GetBlock { // `chainhistoryroot` would be here. Undocumented. TODO: decide if we want to support it // - /// List of transaction IDs in block order, hex-encoded. - // - // TODO: use a typed Vec here - // TODO: support Objects - tx: Vec, + /// List of transactions in block order, hex-encoded if verbosity=1 or + /// as objects if verbosity=2. + tx: Vec, /// The height of the requested block. #[serde(skip_serializing_if = "Option::is_none")] @@ -1803,7 +1821,7 @@ pub enum GetBlock { difficulty: Option, // `chainwork` would be here, but we don't plan on supporting it - // `anchor` would be here. Undocumented. TODO: decide if we want to support it + // `anchor` would be here. Not planned to be supported. // `chainSupply` would be here, TODO: implement // `valuePools` would be here, TODO: implement // @@ -1844,6 +1862,17 @@ impl Default for GetBlock { } } +#[derive(Clone, Debug, PartialEq, serde::Serialize)] +#[serde(untagged)] +/// The transaction list in a `getblock` call. Can be a list of transaction +/// IDs or the full transaction details depending on verbosity. +pub enum GetBlockTransaction { + /// The transaction hash, hex-encoded. + Hash(#[serde(with = "hex")] transaction::Hash), + /// The block object. + Object(TransactionObject), +} + /// Response to a `getblockheader` RPC request. /// /// See the notes for the [`Rpc::get_block_header`] method. @@ -1987,22 +2016,33 @@ pub enum GetRawTransaction { /// The raw transaction, encoded as hex bytes. Raw(#[serde(with = "hex")] SerializedTransaction), /// The transaction object. - Object { - /// The raw transaction, encoded as hex bytes. - #[serde(with = "hex")] - hex: SerializedTransaction, - /// The height of the block in the best chain that contains the transaction, or -1 if - /// the transaction is in the mempool. - height: i32, - /// The confirmations of the block in the best chain that contains the transaction, - /// or 0 if the transaction is in the mempool. - confirmations: u32, - }, + Object(TransactionObject), } impl Default for GetRawTransaction { fn default() -> Self { - Self::Object { + Self::Object(TransactionObject::default()) + } +} + +/// A Transaction object as returnedb by `getrawtransaction` RPC request. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] +pub struct TransactionObject { + /// The raw transaction, encoded as hex bytes. + #[serde(with = "hex")] + pub hex: SerializedTransaction, + /// The height of the block in the best chain that contains the transaction, or -1 if + /// the transaction is in the mempool. + pub height: i32, + /// The confirmations of the block in the best chain that contains the transaction, + /// or 0 if the transaction is in the mempool. + pub confirmations: u32, + // TODO: many fields not yet supported +} + +impl Default for TransactionObject { + fn default() -> Self { + Self { hex: SerializedTransaction::from( [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(), ), @@ -2080,7 +2120,7 @@ impl GetRawTransaction { verbose: bool, ) -> Self { if verbose { - GetRawTransaction::Object { + GetRawTransaction::Object(TransactionObject { hex: tx.into(), height: match height { Some(height) => height @@ -2090,7 +2130,7 @@ impl GetRawTransaction { None => -1, }, confirmations, - } + }) } else { GetRawTransaction::Raw(tx.into()) } diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 007a89ee893..c8b86a98a36 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -176,7 +176,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -219,7 +219,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -262,7 +262,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -305,7 +305,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -348,7 +348,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -391,7 +391,7 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| tx.hash().encode_hex()) + .map(|tx| GetBlockTransaction::Hash(tx.hash())) .collect(), trees, size: None, @@ -766,11 +766,11 @@ async fn rpc_getrawtransaction() { } let (response, _) = futures::join!(get_tx_verbose_1_req, make_mempool_req(tx_hash)); - let GetRawTransaction::Object { + let GetRawTransaction::Object(TransactionObject { hex, height, confirmations, - } = response.expect("We should have a GetRawTransaction struct") + }) = response.expect("We should have a GetRawTransaction struct") else { unreachable!("Should return a Raw enum") }; diff --git a/zebra-rpc/src/tests/vectors.rs b/zebra-rpc/src/tests/vectors.rs index 84ed937d6ef..be075f25d47 100644 --- a/zebra-rpc/src/tests/vectors.rs +++ b/zebra-rpc/src/tests/vectors.rs @@ -1,6 +1,6 @@ //! Fixed Zebra RPC serialization test vectors. -use crate::methods::{GetBlock, GetRawTransaction}; +use crate::methods::{GetBlock, GetRawTransaction, TransactionObject}; #[test] pub fn test_transaction_serialization() { @@ -10,11 +10,11 @@ pub fn test_transaction_serialization() { assert_eq!(j, expected_json); - let expected_tx = GetRawTransaction::Object { + let expected_tx = GetRawTransaction::Object(TransactionObject { hex: vec![0x42].into(), height: 1, confirmations: 0, - }; + }); let expected_json = r#"{"hex":"42","height":1,"confirmations":0}"#; let j = serde_json::to_string(&expected_tx).unwrap(); From 667dad78f1bfdbe4f2d0b95ca70558eba0644d8d Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 11 Dec 2024 22:08:20 -0300 Subject: [PATCH 2/5] fix test --- zebra-rpc/src/methods/tests/vectors.rs | 47 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index c8b86a98a36..5f76c468266 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -242,12 +242,31 @@ async fn rpc_getblock() { ); } + // With verbosity=2, the RPC calls getrawtransaction which queries the + // mempool, which we need to mock since we used a MockService for it. This + // function returns a future that mocks that query. This is similar to what + // we use in the getrawtransaction test, but here we don't bother checking + // if the request is correct. + let make_mempool_req = || { + let mut mempool = mempool.clone(); + async move { + mempool + .expect_request_that(|_request| true) + .await + .respond(mempool::Response::Transactions(vec![])); + } + }; + // Make height calls with verbosity=2 and check response for (i, block) in blocks.iter().enumerate() { - let get_block = rpc - .get_block(i.to_string(), Some(2u8)) - .await - .expect("We should have a GetBlock struct"); + let get_block_req = rpc.get_block(i.to_string(), Some(2u8)); + + // Run both the getblock request and the mocked mempool request. + // This assumes a single mempool query, i.e. that there is a single + // transaction the block. If the test vectors changes and the block + // has more than one transaction, this will need to be adjusted. + let (response, _) = futures::join!(get_block_req, make_mempool_req()); + let get_block = response.expect("We should have a GetBlock struct"); let (expected_nonce, expected_final_sapling_root) = get_block_data(&read_state, block.clone(), i).await; @@ -262,7 +281,11 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| GetBlockTransaction::Hash(tx.hash())) + .map(|tx| GetBlockTransaction::Object(TransactionObject { + hex: (*tx).clone().into(), + height: i.try_into().expect("valid u32"), + confirmations: (blocks.len() - i).try_into().expect("valid i64") + })) .collect(), trees, size: None, @@ -287,10 +310,10 @@ async fn rpc_getblock() { // Make hash calls with verbosity=2 and check response for (i, block) in blocks.iter().enumerate() { - let get_block = rpc - .get_block(blocks[i].hash().to_string(), Some(2u8)) - .await - .expect("We should have a GetBlock struct"); + let get_block_req = rpc.get_block(blocks[i].hash().to_string(), Some(2u8)); + + let (response, _) = futures::join!(get_block_req, make_mempool_req()); + let get_block = response.expect("We should have a GetBlock struct"); let (expected_nonce, expected_final_sapling_root) = get_block_data(&read_state, block.clone(), i).await; @@ -305,7 +328,11 @@ async fn rpc_getblock() { tx: block .transactions .iter() - .map(|tx| GetBlockTransaction::Hash(tx.hash())) + .map(|tx| GetBlockTransaction::Object(TransactionObject { + hex: (*tx).clone().into(), + height: i.try_into().expect("valid u32"), + confirmations: (blocks.len() - i).try_into().expect("valid i64") + })) .collect(), trees, size: None, From a8605771b08ec705792fa1c8ae25d07069840cb3 Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Wed, 11 Dec 2024 22:42:42 -0300 Subject: [PATCH 3/5] use FuturesOrdered --- zebra-rpc/src/methods.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index 4388d72f40a..f4521e5ced6 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -836,7 +836,7 @@ where } let tx_ids_response = futs.next().await.expect("`futs` should not be empty"); - let tx = match tx_ids_response.map_server_error()? { + let tx: Vec<_> = match tx_ids_response.map_server_error()? { zebra_state::ReadResponse::TransactionIdsForBlock(tx_ids) => tx_ids .ok_or_server_error("Block not found")? .iter() @@ -847,13 +847,18 @@ where let tx = if verbosity >= 2 { let mut tx_obj_vec = vec![]; + let mut tx_futs = FuturesOrdered::new(); + let tx_len = tx.len(); for txid in tx { let GetBlockTransaction::Hash(txid) = txid else { unreachable!("must be a Hash") }; - let get_tx_result: Result = self_clone - .get_raw_transaction(txid.encode_hex(), Some(1)) - .await; + tx_futs + .push_back(self_clone.get_raw_transaction(txid.encode_hex(), Some(1))); + } + for _ in 0..tx_len { + let get_tx_result = + tx_futs.next().await.expect("`tx_futs` should not be empty"); let GetRawTransaction::Object(tx_obj) = get_tx_result? else { unreachable!("must return Object"); }; From 08f0721ad12bf0b0e9bb2ca1d36e0cc8e1c5d355 Mon Sep 17 00:00:00 2001 From: Arya Date: Fri, 13 Dec 2024 09:44:48 -0500 Subject: [PATCH 4/5] Suggestion for "add(rpc): getblock: return transaction details with verbosity=2" (#9084) * Replaces multiple service calls (per transaction) with a single call to the state service for all of a block's transactions. * adjustments to reuse code from getrawtransaction --------- Co-authored-by: Conrado Gouvea --- zebra-rpc/src/methods.rs | 53 +++++++++++++------------- zebra-rpc/src/methods/tests/vectors.rs | 35 ++++------------- 2 files changed, 35 insertions(+), 53 deletions(-) diff --git a/zebra-rpc/src/methods.rs b/zebra-rpc/src/methods.rs index f4521e5ced6..012ee13e1e6 100644 --- a/zebra-rpc/src/methods.rs +++ b/zebra-rpc/src/methods.rs @@ -760,7 +760,6 @@ where None }; - let self_clone = self.clone(); async move { let hash_or_height: HashOrHeight = hash_or_height.parse().map_server_error()?; @@ -811,6 +810,12 @@ where next_block_hash, } = *block_header; + let transactions_request = match verbosity { + 1 => zebra_state::ReadRequest::TransactionIdsForBlock(hash_or_height), + 2 => zebra_state::ReadRequest::Block(hash_or_height), + _other => panic!("get_block_header_fut should be none"), + }; + // # Concurrency // // We look up by block hash so the hash, transaction IDs, and confirmations @@ -824,7 +829,7 @@ where // 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_or_height), + transactions_request, // Orchard trees zebra_state::ReadRequest::OrchardTree(hash_or_height), ]; @@ -842,33 +847,29 @@ where .iter() .map(|tx_id| GetBlockTransaction::Hash(*tx_id)) .collect(), + zebra_state::ReadResponse::Block(block) => block + .ok_or_server_error("Block not found")? + .transactions + .iter() + .map(|tx| { + let GetRawTransaction::Object(tx_obj) = + GetRawTransaction::from_transaction( + tx.clone(), + Some(height), + confirmations + .try_into() + .expect("should be less than max block height, i32::MAX"), + true, + ) + else { + unreachable!("an Object must be returned when verbose is true"); + }; + GetBlockTransaction::Object(tx_obj) + }) + .collect(), _ => unreachable!("unmatched response to a transaction_ids_for_block request"), }; - let tx = if verbosity >= 2 { - let mut tx_obj_vec = vec![]; - let mut tx_futs = FuturesOrdered::new(); - let tx_len = tx.len(); - for txid in tx { - let GetBlockTransaction::Hash(txid) = txid else { - unreachable!("must be a Hash") - }; - tx_futs - .push_back(self_clone.get_raw_transaction(txid.encode_hex(), Some(1))); - } - for _ in 0..tx_len { - let get_tx_result = - tx_futs.next().await.expect("`tx_futs` should not be empty"); - let GetRawTransaction::Object(tx_obj) = get_tx_result? else { - unreachable!("must return Object"); - }; - tx_obj_vec.push(GetBlockTransaction::Object(tx_obj)); - } - tx_obj_vec - } else { - tx - }; - let orchard_tree_response = futs.next().await.expect("`futs` should not be empty"); let zebra_state::ReadResponse::OrchardTree(orchard_tree) = orchard_tree_response.map_server_error()? diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 5f76c468266..722e89e5664 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -242,31 +242,12 @@ async fn rpc_getblock() { ); } - // With verbosity=2, the RPC calls getrawtransaction which queries the - // mempool, which we need to mock since we used a MockService for it. This - // function returns a future that mocks that query. This is similar to what - // we use in the getrawtransaction test, but here we don't bother checking - // if the request is correct. - let make_mempool_req = || { - let mut mempool = mempool.clone(); - async move { - mempool - .expect_request_that(|_request| true) - .await - .respond(mempool::Response::Transactions(vec![])); - } - }; - // Make height calls with verbosity=2 and check response for (i, block) in blocks.iter().enumerate() { - let get_block_req = rpc.get_block(i.to_string(), Some(2u8)); - - // Run both the getblock request and the mocked mempool request. - // This assumes a single mempool query, i.e. that there is a single - // transaction the block. If the test vectors changes and the block - // has more than one transaction, this will need to be adjusted. - let (response, _) = futures::join!(get_block_req, make_mempool_req()); - let get_block = response.expect("We should have a GetBlock struct"); + let get_block = rpc + .get_block(i.to_string(), Some(2u8)) + .await + .expect("We should have a GetBlock struct"); let (expected_nonce, expected_final_sapling_root) = get_block_data(&read_state, block.clone(), i).await; @@ -310,10 +291,10 @@ async fn rpc_getblock() { // Make hash calls with verbosity=2 and check response for (i, block) in blocks.iter().enumerate() { - let get_block_req = rpc.get_block(blocks[i].hash().to_string(), Some(2u8)); - - let (response, _) = futures::join!(get_block_req, make_mempool_req()); - let get_block = response.expect("We should have a GetBlock struct"); + let get_block = rpc + .get_block(blocks[i].hash().to_string(), Some(2u8)) + .await + .expect("We should have a GetBlock struct"); let (expected_nonce, expected_final_sapling_root) = get_block_data(&read_state, block.clone(), i).await; From 15df6d10508c480ee64380f6d849acd427b4a43b Mon Sep 17 00:00:00 2001 From: Conrado Gouvea Date: Fri, 13 Dec 2024 17:44:52 -0300 Subject: [PATCH 5/5] update snapshot --- .../get_block_verbose_hash_verbosity_2@mainnet_10.snap | 6 +++++- .../get_block_verbose_hash_verbosity_2@testnet_10.snap | 6 +++++- .../get_block_verbose_height_verbosity_2@mainnet_10.snap | 6 +++++- .../get_block_verbose_height_verbosity_2@testnet_10.snap | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@mainnet_10.snap index 93010ad42d4..51729b13293 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@mainnet_10.snap @@ -10,7 +10,11 @@ expression: block "merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", "tx": [ - "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609" + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000", + "height": 1, + "confirmations": 10 + } ], "time": 1477671596, "nonce": "9057977ea6d4ae867decc96359fcf2db8cdebcbfb3bd549de4f21f16cfe83475", diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@testnet_10.snap index 5bd22590f1b..51bbfc72f05 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_hash_verbosity_2@testnet_10.snap @@ -10,7 +10,11 @@ expression: block "merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", "tx": [ - "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75" + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000", + "height": 1, + "confirmations": 10 + } ], "time": 1477674473, "nonce": "0000e5739438a096ca89cde16bcf6001e0c5a7ce6f7c591d26314c26c2560000", diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@mainnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@mainnet_10.snap index 93010ad42d4..51729b13293 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@mainnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@mainnet_10.snap @@ -10,7 +10,11 @@ expression: block "merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609", "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", "tx": [ - "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609" + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000", + "height": 1, + "confirmations": 10 + } ], "time": 1477671596, "nonce": "9057977ea6d4ae867decc96359fcf2db8cdebcbfb3bd549de4f21f16cfe83475", diff --git a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@testnet_10.snap b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@testnet_10.snap index 5bd22590f1b..51bbfc72f05 100644 --- a/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@testnet_10.snap +++ b/zebra-rpc/src/methods/tests/snapshots/get_block_verbose_height_verbosity_2@testnet_10.snap @@ -10,7 +10,11 @@ expression: block "merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75", "finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000", "tx": [ - "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75" + { + "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000", + "height": 1, + "confirmations": 10 + } ], "time": 1477674473, "nonce": "0000e5739438a096ca89cde16bcf6001e0c5a7ce6f7c591d26314c26c2560000",