Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add(rpc): getblock: return transaction details with verbosity=2 #9083

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 69 additions & 23 deletions zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -810,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
Expand All @@ -823,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),
];
Expand All @@ -835,11 +841,31 @@ 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()
.map(|tx_id| tx_id.encode_hex())
.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"),
};
Expand Down Expand Up @@ -1771,11 +1797,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<transaction::Hash> here
// TODO: support Objects
tx: Vec<String>,
/// List of transactions in block order, hex-encoded if verbosity=1 or
/// as objects if verbosity=2.
tx: Vec<GetBlockTransaction>,

/// The height of the requested block.
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -1803,7 +1827,7 @@ pub enum GetBlock {
difficulty: Option<f64>,

// `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
//
Expand Down Expand Up @@ -1844,6 +1868,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.
Expand Down Expand Up @@ -1987,22 +2022,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// A Transaction object as returnedb by `getrawtransaction` RPC request.
/// A Transaction object as returned 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(),
),
Expand Down Expand Up @@ -2080,7 +2126,7 @@ impl GetRawTransaction {
verbose: bool,
) -> Self {
if verbose {
GetRawTransaction::Object {
GetRawTransaction::Object(TransactionObject {
hex: tx.into(),
height: match height {
Some(height) => height
Expand All @@ -2090,7 +2136,7 @@ impl GetRawTransaction {
None => -1,
},
confirmations,
}
})
} else {
GetRawTransaction::Raw(tx.into())
}
Expand Down
24 changes: 16 additions & 8 deletions zebra-rpc/src/methods/tests/vectors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -262,7 +262,11 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.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,
Expand Down Expand Up @@ -305,7 +309,11 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.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,
Expand Down Expand Up @@ -348,7 +356,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,
Expand Down Expand Up @@ -391,7 +399,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,
Expand Down Expand Up @@ -766,11 +774,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")
};
Expand Down
6 changes: 3 additions & 3 deletions zebra-rpc/src/tests/vectors.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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();

Expand Down
Loading