diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index c0b97c7dc83..e7b1610aa73 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -26,9 +26,8 @@ use crate::methods::{ DEFAULT_SOLUTION_RATE_WINDOW_SIZE, GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, }, get_block_template::{ - check_block_template_parameters, check_miner_address, check_synced_to_tip, - fetch_mempool_transactions, fetch_state_tip_and_local_time, - generate_coinbase_and_roots, + check_miner_address, check_synced_to_tip, fetch_mempool_transactions, + fetch_state_tip_and_local_time, generate_coinbase_and_roots, }, types::{ get_block_template::GetBlockTemplate, get_mining_info, hex_data::HexData, @@ -101,7 +100,7 @@ pub trait GetBlockTemplateRpc { fn get_block_template( &self, parameters: Option, - ) -> BoxFuture>; + ) -> BoxFuture>; /// Submits block to the node to be validated and committed. /// Returns the [`submit_block::Response`] for the operation, as a JSON string. @@ -316,7 +315,7 @@ where fn get_block_template( &self, parameters: Option, - ) -> BoxFuture> { + ) -> BoxFuture> { // Clone Config let network = self.network; let miner_address = self.miner_address; @@ -326,256 +325,284 @@ where let mut latest_chain_tip = self.latest_chain_tip.clone(); let sync_status = self.sync_status.clone(); let state = self.state.clone(); + let chain_verifier = get_block_template::JsonParameters::is_proposal_mode(¶meters) + .then(|| self.chain_verifier.clone()); // To implement long polling correctly, we split this RPC into multiple phases. async move { - // - One-off checks - - // Check config and parameters. - // These checks always have the same result during long polling. - let miner_address = check_miner_address(miner_address)?; - - let mut client_long_poll_id = None; - if let Some(parameters) = parameters { - check_block_template_parameters(¶meters)?; - - client_long_poll_id = parameters.long_poll_id; - } - - // - Checks and fetches that can change during long polling - // - // Set up the loop. - let mut max_time_reached = false; - - // The loop returns the server long poll ID, - // which should be different to the client long poll ID. - let (server_long_poll_id, chain_tip_and_local_time, mempool_txs, submit_old) = loop { - // Check if we are synced to the tip. - // The result of this check can change during long polling. - // - // Optional TODO: - // - add `async changed()` method to ChainSyncStatus (like `ChainTip`) - check_synced_to_tip(network, latest_chain_tip.clone(), sync_status.clone())?; - - // We're just about to fetch state data, then maybe wait for any changes. - // Mark all the changes before the fetch as seen. - // Changes are also ignored in any clones made after the mark. - latest_chain_tip.mark_best_tip_seen(); - - // Fetch the state data and local time for the block template: - // - if the tip block hash changes, we must return from long polling, - // - if the local clock changes on testnet, we might return from long polling + let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id.clone()); + + if let Some(HexData(block_proposal_bytes)) = + get_block_template::JsonParameters::block_proposal(parameters)? + { + let mut chain_verifier = + chain_verifier.expect("chain_verifier is Some in proposal mode"); + + let block: Block = match block_proposal_bytes.zcash_deserialize_into() { + Ok(block_bytes) => block_bytes, + Err(_) => return Ok(get_block_template::ProposalRejectReason::Rejected.into()), + }; + + let chain_verifier_response = chain_verifier + .ready() + .await + .map_err(|error| Error { + code: ErrorCode::ServerError(0), + message: error.to_string(), + data: None, + })? + .call(zebra_consensus::Request::CheckProposal(Arc::new(block))) + .await; + + let response = chain_verifier_response + .map(|_| get_block_template::ProposalResponse::Valid) + .unwrap_or_else(|_| get_block_template::ProposalRejectReason::Rejected.into()); + + Ok(get_block_template::Response::ProposalMode(response)) + } else { + // - One-off checks + + // Check config and parameters. + // These checks always have the same result during long polling. + let miner_address = check_miner_address(miner_address)?; + + // - Checks and fetches that can change during long polling // - // We always return after 90 minutes on mainnet, even if we have the same response, - // because the max time has been reached. - let chain_tip_and_local_time = - fetch_state_tip_and_local_time(state.clone()).await?; + // Set up the loop. + let mut max_time_reached = false; + + // The loop returns the server long poll ID, + // which should be different to the client long poll ID. + let (server_long_poll_id, chain_tip_and_local_time, mempool_txs, submit_old) = loop { + // Check if we are synced to the tip. + // The result of this check can change during long polling. + // + // Optional TODO: + // - add `async changed()` method to ChainSyncStatus (like `ChainTip`) + check_synced_to_tip(network, latest_chain_tip.clone(), sync_status.clone())?; + + // We're just about to fetch state data, then maybe wait for any changes. + // Mark all the changes before the fetch as seen. + // Changes are also ignored in any clones made after the mark. + latest_chain_tip.mark_best_tip_seen(); + + // Fetch the state data and local time for the block template: + // - if the tip block hash changes, we must return from long polling, + // - if the local clock changes on testnet, we might return from long polling + // + // We always return after 90 minutes on mainnet, even if we have the same response, + // because the max time has been reached. + let chain_tip_and_local_time = + fetch_state_tip_and_local_time(state.clone()).await?; + + // Fetch the mempool data for the block template: + // - if the mempool transactions change, we might return from long polling. + // + // If the chain fork has just changed, miners want to get the new block as fast + // as possible, rather than wait for transactions to re-verify. This increases + // miner profits (and any delays can cause chain forks). So we don't wait between + // the chain tip changing and getting mempool transactions. + // + // Optional TODO: + // - add a `MempoolChange` type with an `async changed()` method (like `ChainTip`) + let mempool_txs = fetch_mempool_transactions(mempool.clone()).await?; + + // - Long poll ID calculation + let server_long_poll_id = LongPollInput::new( + chain_tip_and_local_time.tip_height, + chain_tip_and_local_time.tip_hash, + chain_tip_and_local_time.max_time, + mempool_txs.iter().map(|tx| tx.transaction.id), + ) + .generate_id(); + + // The loop finishes if: + // - the client didn't pass a long poll ID, + // - the server long poll ID is different to the client long poll ID, or + // - the previous loop iteration waited until the max time. + if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached { + let mut submit_old = client_long_poll_id + .as_ref() + .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id)); + + // On testnet, the max time changes the block difficulty, so old shares are + // invalid. On mainnet, this means there has been 90 minutes without a new + // block or mempool transaction, which is very unlikely. So the miner should + // probably reset anyway. + if max_time_reached { + submit_old = Some(false); + } - // Fetch the mempool data for the block template: - // - if the mempool transactions change, we might return from long polling. - // - // If the chain fork has just changed, miners want to get the new block as fast - // as possible, rather than wait for transactions to re-verify. This increases - // miner profits (and any delays can cause chain forks). So we don't wait between - // the chain tip changing and getting mempool transactions. - // - // Optional TODO: - // - add a `MempoolChange` type with an `async changed()` method (like `ChainTip`) - let mempool_txs = fetch_mempool_transactions(mempool.clone()).await?; - - // - Long poll ID calculation - let server_long_poll_id = LongPollInput::new( - chain_tip_and_local_time.tip_height, - chain_tip_and_local_time.tip_hash, - chain_tip_and_local_time.max_time, - mempool_txs.iter().map(|tx| tx.transaction.id), - ) - .generate_id(); - - // The loop finishes if: - // - the client didn't pass a long poll ID, - // - the server long poll ID is different to the client long poll ID, or - // - the previous loop iteration waited until the max time. - if Some(&server_long_poll_id) != client_long_poll_id.as_ref() || max_time_reached { - let mut submit_old = client_long_poll_id - .as_ref() - .map(|old_long_poll_id| server_long_poll_id.submit_old(old_long_poll_id)); - - // On testnet, the max time changes the block difficulty, so old shares are - // invalid. On mainnet, this means there has been 90 minutes without a new - // block or mempool transaction, which is very unlikely. So the miner should - // probably reset anyway. - if max_time_reached { - submit_old = Some(false); + break ( + server_long_poll_id, + chain_tip_and_local_time, + mempool_txs, + submit_old, + ); } - break ( - server_long_poll_id, - chain_tip_and_local_time, - mempool_txs, - submit_old, - ); - } - - // - Polling wait conditions - // - // TODO: when we're happy with this code, split it into a function. - // - // Periodically check the mempool for changes. - // - // Optional TODO: - // Remove this polling wait if we switch to using futures to detect sync status - // and mempool changes. - let wait_for_mempool_request = tokio::time::sleep(Duration::from_secs( - GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, - )); - - // Return immediately if the chain tip has changed. - let wait_for_best_tip_change = latest_chain_tip.best_tip_changed(); - - // Wait for the maximum block time to elapse. This can change the block header - // on testnet. (On mainnet it can happen due to a network disconnection, or a - // rapid drop in hash rate.) - // - // This duration might be slightly lower than the actual maximum, - // if cur_time was clamped to min_time. In that case the wait is very long, - // and it's ok to return early. - // - // It can also be zero if cur_time was clamped to max_time. In that case, - // we want to wait for another change, and ignore this timeout. So we use an - // `OptionFuture::None`. - let duration_until_max_time = chain_tip_and_local_time - .max_time - .saturating_duration_since(chain_tip_and_local_time.cur_time); - let wait_for_max_time: OptionFuture<_> = if duration_until_max_time.seconds() > 0 { - Some(tokio::time::sleep(duration_until_max_time.to_std())) - } else { - None - } - .into(); - - // Optional TODO: - // `zcashd` generates the next coinbase transaction while waiting for changes. - // When Zebra supports shielded coinbase, we might want to do this in parallel. - // But the coinbase value depends on the selected transactions, so this needs - // further analysis to check if it actually saves us any time. - - // TODO: change logging to debug after testing - tokio::select! { - // Poll the futures in the listed order, for efficiency. - // We put the most frequent conditions first. - biased; - - // This timer elapses every few seconds - _elapsed = wait_for_mempool_request => { - tracing::info!( - max_time = ?chain_tip_and_local_time.max_time, - cur_time = ?chain_tip_and_local_time.cur_time, - ?server_long_poll_id, - ?client_long_poll_id, - GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, - "checking for a new mempool change after waiting a few seconds" - ); + // - Polling wait conditions + // + // TODO: when we're happy with this code, split it into a function. + // + // Periodically check the mempool for changes. + // + // Optional TODO: + // Remove this polling wait if we switch to using futures to detect sync status + // and mempool changes. + let wait_for_mempool_request = tokio::time::sleep(Duration::from_secs( + GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, + )); + + // Return immediately if the chain tip has changed. + let wait_for_best_tip_change = latest_chain_tip.best_tip_changed(); + + // Wait for the maximum block time to elapse. This can change the block header + // on testnet. (On mainnet it can happen due to a network disconnection, or a + // rapid drop in hash rate.) + // + // This duration might be slightly lower than the actual maximum, + // if cur_time was clamped to min_time. In that case the wait is very long, + // and it's ok to return early. + // + // It can also be zero if cur_time was clamped to max_time. In that case, + // we want to wait for another change, and ignore this timeout. So we use an + // `OptionFuture::None`. + let duration_until_max_time = chain_tip_and_local_time + .max_time + .saturating_duration_since(chain_tip_and_local_time.cur_time); + let wait_for_max_time: OptionFuture<_> = if duration_until_max_time.seconds() > 0 { + Some(tokio::time::sleep(duration_until_max_time.to_std())) + } else { + None } + .into(); + + // Optional TODO: + // `zcashd` generates the next coinbase transaction while waiting for changes. + // When Zebra supports shielded coinbase, we might want to do this in parallel. + // But the coinbase value depends on the selected transactions, so this needs + // further analysis to check if it actually saves us any time. + + // TODO: change logging to debug after testing + tokio::select! { + // Poll the futures in the listed order, for efficiency. + // We put the most frequent conditions first. + biased; + + // This timer elapses every few seconds + _elapsed = wait_for_mempool_request => { + tracing::info!( + max_time = ?chain_tip_and_local_time.max_time, + cur_time = ?chain_tip_and_local_time.cur_time, + ?server_long_poll_id, + ?client_long_poll_id, + GET_BLOCK_TEMPLATE_MEMPOOL_LONG_POLL_INTERVAL, + "checking for a new mempool change after waiting a few seconds" + ); + } - // The state changes after around a target block interval (75s) - tip_changed_result = wait_for_best_tip_change => { - match tip_changed_result { - Ok(()) => { - tracing::info!( - max_time = ?chain_tip_and_local_time.max_time, - cur_time = ?chain_tip_and_local_time.cur_time, - ?server_long_poll_id, - ?client_long_poll_id, - "returning from long poll because state has changed" - ); + // The state changes after around a target block interval (75s) + tip_changed_result = wait_for_best_tip_change => { + match tip_changed_result { + Ok(()) => { + tracing::info!( + max_time = ?chain_tip_and_local_time.max_time, + cur_time = ?chain_tip_and_local_time.cur_time, + ?server_long_poll_id, + ?client_long_poll_id, + "returning from long poll because state has changed" + ); + } + + Err(recv_error) => { + // This log should stay at info when the others go to debug, + // it will help with debugging. + tracing::info!( + ?recv_error, + max_time = ?chain_tip_and_local_time.max_time, + cur_time = ?chain_tip_and_local_time.cur_time, + ?server_long_poll_id, + ?client_long_poll_id, + "returning from long poll due to a state error.\ + Is Zebra shutting down?" + ); + + return Err(Error { + code: ErrorCode::ServerError(0), + message: recv_error.to_string(), + data: None, + }); + } } + } - Err(recv_error) => { - // This log should stay at info when the others go to debug, - // it will help with debugging. - tracing::info!( - ?recv_error, - max_time = ?chain_tip_and_local_time.max_time, - cur_time = ?chain_tip_and_local_time.cur_time, - ?server_long_poll_id, - ?client_long_poll_id, - "returning from long poll due to a state error.\ - Is Zebra shutting down?" - ); - - return Err(Error { - code: ErrorCode::ServerError(0), - message: recv_error.to_string(), - data: None, - }); - } + // The max time does not elapse during normal operation on mainnet, + // and it rarely elapses on testnet. + Some(_elapsed) = wait_for_max_time => { + // This log should stay at info when the others go to debug, + // it's very rare. + tracing::info!( + max_time = ?chain_tip_and_local_time.max_time, + cur_time = ?chain_tip_and_local_time.cur_time, + ?server_long_poll_id, + ?client_long_poll_id, + "returning from long poll because max time was reached" + ); + + max_time_reached = true; } } + }; - // The max time does not elapse during normal operation on mainnet, - // and it rarely elapses on testnet. - Some(_elapsed) = wait_for_max_time => { - // This log should stay at info when the others go to debug, - // it's very rare. - tracing::info!( - max_time = ?chain_tip_and_local_time.max_time, - cur_time = ?chain_tip_and_local_time.cur_time, - ?server_long_poll_id, - ?client_long_poll_id, - "returning from long poll because max time was reached" - ); + // - Processing fetched data to create a transaction template + // + // Apart from random weighted transaction selection, + // the template only depends on the previously fetched data. + // This processing never fails. - max_time_reached = true; - } - } - }; + // Calculate the next block height. + let next_block_height = + (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX"); - // - Processing fetched data to create a transaction template - // - // Apart from random weighted transaction selection, - // the template only depends on the previously fetched data. - // This processing never fails. + // Randomly select some mempool transactions. + // + // TODO: sort these transactions to match zcashd's order, to make testing easier. + let mempool_txs = zip317::select_mempool_transactions( + network, + next_block_height, + miner_address, + mempool_txs, + ) + .await; - // Calculate the next block height. - let next_block_height = - (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX"); + // - After this point, the template only depends on the previously fetched data. - // Randomly select some mempool transactions. - // - // TODO: sort these transactions to match zcashd's order, to make testing easier. - let mempool_txs = zip317::select_mempool_transactions( - network, - next_block_height, - miner_address, - mempool_txs, - ) - .await; - - // - After this point, the template only depends on the previously fetched data. - - // Generate the coinbase transaction and default roots - // - // TODO: move expensive root, hash, and tree cryptography to a rayon thread? - let (coinbase_txn, default_roots) = generate_coinbase_and_roots( - network, - next_block_height, - miner_address, - &mempool_txs, - chain_tip_and_local_time.history_tree.clone(), - ); - - let response = GetBlockTemplate::new( - next_block_height, - &chain_tip_and_local_time, - server_long_poll_id, - coinbase_txn, - &mempool_txs, - default_roots, - submit_old, - ); - - Ok(response) + // Generate the coinbase transaction and default roots + // + // TODO: move expensive root, hash, and tree cryptography to a rayon thread? + let (coinbase_txn, default_roots) = generate_coinbase_and_roots( + network, + next_block_height, + miner_address, + &mempool_txs, + chain_tip_and_local_time.history_tree.clone(), + ); + + let response = GetBlockTemplate::new( + next_block_height, + &chain_tip_and_local_time, + server_long_poll_id, + coinbase_txn, + &mempool_txs, + default_roots, + submit_old, + ); + + Ok(get_block_template::Response::TemplateMode(Box::new( + response, + ))) + } } .boxed() } diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs index 8af438884a7..9b6225a0a6c 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/get_block_template.rs @@ -28,21 +28,70 @@ use crate::methods::get_block_template_rpcs::{ pub use crate::methods::get_block_template_rpcs::types::get_block_template::*; +use super::types::hex_data::HexData; + // - Parameter checks -/// Returns an error if the get block template RPC `parameters` are invalid. -pub fn check_block_template_parameters( - parameters: &get_block_template::JsonParameters, -) -> Result<()> { - if parameters.data.is_some() || parameters.mode == GetBlockTemplateRequestMode::Proposal { - return Err(Error { - code: ErrorCode::InvalidParams, - message: "\"proposal\" mode is currently unsupported by Zebra".to_string(), - data: None, - }); +impl JsonParameters { + /// Checks that `data` is omitted in `Template` mode or provided in `Proposal` mode, + /// + /// Returns an error if there's a mismatch between the mode and whether `data` is provided. + /// Returns Ok(Some(data)) with the block proposal hexdata if in `Proposal` mode. + /// Returns Ok(None) if in `Template` mode. + pub fn block_proposal(parameters: Option) -> Result> { + let Some(parameters) = parameters else { + return Ok(None) + }; + + match parameters { + Self { + mode: GetBlockTemplateRequestMode::Template, + data: None, + .. + } => Ok(None), + + Self { + mode: GetBlockTemplateRequestMode::Proposal, + data: data @ Some(_), + .. + } => Ok(data), + + Self { + mode: GetBlockTemplateRequestMode::Proposal, + data: None, + .. + } => Err(Error { + code: ErrorCode::InvalidParams, + message: "\"data\" parameter must be \ + provided in \"proposal\" mode" + .to_string(), + data: None, + }), + + Self { + mode: GetBlockTemplateRequestMode::Template, + data: Some(_), + .. + } => Err(Error { + code: ErrorCode::InvalidParams, + message: "\"data\" parameter must be \ + omitted in \"template\" mode" + .to_string(), + data: None, + }), + } } - Ok(()) + /// Checks if `mode` parameters is `Proposal`. + pub fn is_proposal_mode(parameters: &Option) -> bool { + matches!( + parameters, + Some(get_block_template::JsonParameters { + mode: GetBlockTemplateRequestMode::Proposal, + .. + }) + ) + } } /// Returns the miner address, or an error if it is invalid. diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs index 77134e16c56..08cfe80d03d 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template.rs @@ -27,7 +27,7 @@ pub mod parameters; pub use parameters::*; -/// A serialized `getblocktemplate` RPC response. +/// A serialized `getblocktemplate` RPC response in template mode. #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] pub struct GetBlockTemplate { /// The getblocktemplate RPC capabilities supported by Zebra. @@ -234,3 +234,60 @@ impl GetBlockTemplate { } } } + +/// Error response to a `getblocktemplate` RPC request in proposal mode. +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ProposalRejectReason { + /// Block rejected as invalid + Rejected, +} + +/// Response to a `getblocktemplate` RPC request in proposal mode. +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(untagged, rename_all = "kebab-case")] +pub enum ProposalResponse { + /// Block was not successfully submitted, return error + ErrorResponse { + /// Reason the proposal was invalid as-is + reject_reason: ProposalRejectReason, + + /// The getblocktemplate RPC capabilities supported by Zebra. + capabilities: Vec, + }, + + /// Block successfully proposed, returns null + Valid, +} + +#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +/// A `getblocktemplate` RPC response. +pub enum Response { + /// `getblocktemplate` RPC request in template mode. + TemplateMode(Box), + + /// `getblocktemplate` RPC request in proposal mode. + ProposalMode(ProposalResponse), +} + +impl From for ProposalResponse { + fn from(reject_reason: ProposalRejectReason) -> Self { + // Convert default values + let capabilities: Vec = GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD + .iter() + .map(ToString::to_string) + .collect(); + + Self::ErrorResponse { + reject_reason, + capabilities, + } + } +} + +impl From for Response { + fn from(error_response: ProposalRejectReason) -> Self { + Self::ProposalMode(ProposalResponse::from(error_response)) + } +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs index 01152891816..d693b2a18ff 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template/parameters.rs @@ -11,7 +11,6 @@ pub enum GetBlockTemplateRequestMode { Template, /// Indicates a request to validate block data. - /// Currently unsupported and will return an error. Proposal, } @@ -68,14 +67,14 @@ pub struct JsonParameters { /// Defines whether the RPC method should generate a block template or attempt to /// validate block data, checking against all of the server's usual acceptance rules /// (excluding the check for a valid proof-of-work). - // TODO: Support `proposal` mode. #[serde(default)] pub mode: GetBlockTemplateRequestMode, - /// Must be omitted as "proposal" mode is currently unsupported. + /// Must be omitted when `getblocktemplate` RPC is called in "template" mode (or when `mode` is omitted). + /// Must be provided when `getblocktemplate` RPC is called in "proposal" mode. /// /// Hex-encoded block data to be validated and checked against the server's usual acceptance rules - /// (excluding the check for a valid proof-of-work) when `mode` is set to `proposal`. + /// (excluding the check for a valid proof-of-work). pub data: Option, /// A list of client-side supported capability features diff --git a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs index 2da8102dfb7..f1e6b4b49ee 100644 --- a/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs @@ -195,10 +195,12 @@ pub async fn test_responses( .await .respond(mempool::Response::FullTransactions(vec![])); - let get_block_template = get_block_template + let get_block_template::Response::TemplateMode(get_block_template) = get_block_template .await .expect("unexpected panic in getblocktemplate RPC task") - .expect("unexpected error in getblocktemplate RPC call"); + .expect("unexpected error in getblocktemplate RPC call") else { + panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") + }; let coinbase_tx: Transaction = get_block_template .coinbase_txn @@ -250,10 +252,12 @@ pub async fn test_responses( .await .respond(mempool::Response::FullTransactions(vec![])); - let get_block_template = get_block_template + let get_block_template::Response::TemplateMode(get_block_template) = get_block_template .await .expect("unexpected panic in getblocktemplate RPC task") - .expect("unexpected error in getblocktemplate RPC call"); + .expect("unexpected error in getblocktemplate RPC call") else { + panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") + }; let coinbase_tx: Transaction = get_block_template .coinbase_txn @@ -287,7 +291,7 @@ fn snapshot_rpc_getblockhash(block_hash: GetBlockHash, settings: &insta::Setting /// Snapshot `getblocktemplate` response, using `cargo insta` and JSON serialization. fn snapshot_rpc_getblocktemplate( variant: &'static str, - block_template: GetBlockTemplate, + block_template: Box, coinbase_tx: Transaction, settings: &insta::Settings, ) { diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 465917319da..385464822f0 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -996,7 +996,7 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { .await .respond(mempool::Response::FullTransactions(vec![])); - let get_block_template = get_block_template + let get_block_template::Response::TemplateMode(get_block_template) = get_block_template .await .unwrap_or_else(|error| match error.try_into_panic() { Ok(panic_object) => panic::resume_unwind(panic_object), @@ -1004,7 +1004,9 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) { panic!("getblocktemplate task was unexpectedly cancelled: {cancelled_error:?}") } }) - .expect("unexpected error in getblocktemplate RPC call"); + .expect("unexpected error in getblocktemplate RPC call") else { + panic!("this getblocktemplate call without parameters should return the `TemplateMode` variant of the response") + }; assert_eq!( get_block_template.capabilities,