diff --git a/zebra-rpc/src/methods/get_block_template_rpcs.rs b/zebra-rpc/src/methods/get_block_template_rpcs.rs index c7ddda99e..ce1ce99c5 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs.rs @@ -33,8 +33,9 @@ use zebra_state::{ReadRequest, ReadResponse}; use crate::methods::{ best_chain_tip_height, get_block_template_rpcs::types::{ - default_roots::DefaultRoots, get_block_template::GetBlockTemplate, hex_data::HexData, - submit_block, transaction::TransactionTemplate, + default_roots::DefaultRoots, get_block_template::GetBlockTemplate, + get_block_template_opts::GetBlockTemplateRequestMode, hex_data::HexData, submit_block, + transaction::TransactionTemplate, }, GetBlockHash, MISSING_BLOCK_ERROR_CODE, }; @@ -42,7 +43,7 @@ use crate::methods::{ pub mod config; pub mod constants; -pub(crate) mod types; +pub mod types; pub(crate) mod zip317; /// The max estimated distance to the chain tip for the getblocktemplate method. @@ -113,7 +114,10 @@ pub trait GetBlockTemplateRpc { /// /// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`. #[rpc(name = "getblocktemplate")] - fn get_block_template(&self) -> BoxFuture>; + fn get_block_template( + &self, + options: Option, + ) -> BoxFuture>; /// Submits block to the node to be validated and committed. /// Returns the [`submit_block::Response`] for the operation, as a JSON string. @@ -292,7 +296,10 @@ where .boxed() } - fn get_block_template(&self) -> BoxFuture> { + fn get_block_template( + &self, + options: Option, + ) -> BoxFuture> { let network = self.network; let miner_address = self.miner_address; @@ -303,6 +310,24 @@ where // Since this is a very large RPC, we use separate functions for each group of fields. async move { + if let Some(options) = options { + if options.data.is_some() || options.mode == GetBlockTemplateRequestMode::Proposal { + return Err(Error { + code: ErrorCode::InvalidParams, + message: "\"proposal\" mode is currently unsupported by Zebra".to_string(), + data: None, + }) + } + + if options.longpollid.is_some() { + return Err(Error { + code: ErrorCode::InvalidParams, + message: "long polling is currently unsupported by Zebra".to_string(), + data: None, + }) + } + } + let miner_address = miner_address.ok_or_else(|| Error { code: ErrorCode::ServerError(0), message: "configure mining.miner_address in zebrad.toml \ diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs index 848ea9ae6..989599dca 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types.rs @@ -1,7 +1,8 @@ //! Types used in mining RPC methods. -pub(crate) mod default_roots; -pub(crate) mod get_block_template; -pub(crate) mod hex_data; -pub(crate) mod submit_block; -pub(crate) mod transaction; +pub mod default_roots; +pub mod get_block_template; +pub mod get_block_template_opts; +pub mod hex_data; +pub mod submit_block; +pub mod transaction; diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs new file mode 100644 index 000000000..2718a081b --- /dev/null +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/get_block_template_opts.rs @@ -0,0 +1,91 @@ +//! Parameter types for the `getblocktemplate` RPC. + +use super::hex_data::HexData; + +/// Defines whether the RPC method should generate a block template or attempt to validate a block proposal. +/// `Proposal` mode is currently unsupported and will return an error. +#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum GetBlockTemplateRequestMode { + /// Indicates a request for a block template. + Template, + + /// Indicates a request to validate block data. + /// Currently unsupported and will return an error. + Proposal, +} + +impl Default for GetBlockTemplateRequestMode { + fn default() -> Self { + Self::Template + } +} + +/// Valid `capabilities` values that indicate client-side support. +#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum GetBlockTemplateCapability { + /// Long Polling support. + /// Currently ignored by zebra. + LongPoll, + + /// Information for coinbase transaction, default template data with the `coinbasetxn` field. + /// Currently ignored by zebra. + CoinbaseTxn, + + /// Coinbase value, template response provides a `coinbasevalue` field and omits `coinbasetxn` field. + /// Currently ignored by zebra. + CoinbaseValue, + + /// Components of the coinbase transaction. + /// Currently ignored by zebra. + CoinbaseAux, + + /// Currently ignored by zcashd and zebra. + Proposal, + + /// Currently ignored by zcashd and zebra. + ServerList, + + /// Currently ignored by zcashd and zebra. + WorkId, + + /// Unknown capability to fill in for mutations. + // TODO: Fill out valid mutations capabilities. + #[serde(other)] + UnknownCapability, +} + +/// Optional argument `jsonrequestobject` for `getblocktemplate` RPC request. +/// +/// The `data` field must be provided in `proposal` mode, and must be omitted in `template` mode. +/// All other fields are optional. +#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize, Default)] +pub struct JsonParameters { + /// Must be set to "template" or omitted, as "proposal" mode is currently unsupported. + /// + /// 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. + /// + /// 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`. + pub data: Option, + + /// A list of client-side supported capability features + // TODO: Fill out valid mutations capabilities. + #[serde(default)] + pub capabilities: Vec, + + /// An id to wait for, in zcashd this is the tip hash and an internal counter. + /// + /// If provided, the RPC response is delayed until the mempool or chain tip block changes. + /// + /// Currently unsupported and ignored by Zebra. + pub longpollid: Option, +} diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs index bd203b615..365b8a2f7 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/hex_data.rs @@ -2,5 +2,5 @@ //! for the `submitblock` RPC method. /// Deserialize hex-encoded strings to bytes. -#[derive(Debug, PartialEq, Eq, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub struct HexData(#[serde(with = "hex")] pub Vec); diff --git a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs index 65eb7744c..e135b0a55 100644 --- a/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs +++ b/zebra-rpc/src/methods/get_block_template_rpcs/types/submit_block.rs @@ -7,9 +7,23 @@ use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc; /// Optional argument `jsonparametersobject` for `submitblock` RPC request /// /// See notes for [`GetBlockTemplateRpc::submit_block`] method -#[derive(Debug, serde::Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)] pub struct JsonParameters { - pub(crate) _work_id: Option, + /// The workid for the block template. + /// + /// > If the server provided a workid, it MUST be included with submissions, + /// currently unused. + /// + /// Rationale: + /// + /// > If servers allow all mutations, it may be hard to identify which job it is based on. + /// > While it may be possible to verify the submission by its content, it is much easier + /// > to compare it to the job issued. It is very easy for the miner to keep track of this. + /// > Therefore, using a "workid" is a very cheap solution to enable more mutations. + /// + /// + #[serde(rename = "workid")] + pub _work_id: Option, } /// Response to a `submitblock` RPC request. 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 df67bcc7a..aae7c3636 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 @@ -163,7 +163,7 @@ pub async fn test_responses( }))); }); - let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template()); + let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(None)); mempool .expect_request(mempool::Request::FullTransactions) diff --git a/zebra-rpc/src/methods/tests/vectors.rs b/zebra-rpc/src/methods/tests/vectors.rs index 9c28085b1..a7b976cdc 100644 --- a/zebra-rpc/src/methods/tests/vectors.rs +++ b/zebra-rpc/src/methods/tests/vectors.rs @@ -865,7 +865,7 @@ async fn rpc_getblocktemplate() { }))); }); - let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template()); + let get_block_template = tokio::spawn(get_block_template_rpc.get_block_template(None)); mempool .expect_request(mempool::Request::FullTransactions) @@ -921,7 +921,7 @@ async fn rpc_getblocktemplate() { mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200)); let get_block_template_sync_error = get_block_template_rpc - .get_block_template() + .get_block_template(None) .await .expect_err("needs an error when estimated distance to network chain tip is far"); @@ -934,7 +934,7 @@ async fn rpc_getblocktemplate() { mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(0)); let get_block_template_sync_error = get_block_template_rpc - .get_block_template() + .get_block_template(None) .await .expect_err("needs an error when syncer is not close to tip"); @@ -945,7 +945,7 @@ async fn rpc_getblocktemplate() { mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200)); let get_block_template_sync_error = get_block_template_rpc - .get_block_template() + .get_block_template(None) .await .expect_err("needs an error when syncer is not close to tip or estimated distance to network chain tip is far"); @@ -953,6 +953,39 @@ async fn rpc_getblocktemplate() { get_block_template_sync_error.code, ErrorCode::ServerError(-10) ); + let get_block_template_sync_error = get_block_template_rpc + .get_block_template(Some(get_block_template_rpcs::types::get_block_template_opts::JsonParameters { + mode: get_block_template_rpcs::types::get_block_template_opts::GetBlockTemplateRequestMode::Proposal, + ..Default::default() + })) + .await + .expect_err("needs an error when using unsupported mode"); + + assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams); + + let get_block_template_sync_error = get_block_template_rpc + .get_block_template(Some( + get_block_template_rpcs::types::get_block_template_opts::JsonParameters { + data: Some(get_block_template_rpcs::types::hex_data::HexData("".into())), + ..Default::default() + }, + )) + .await + .expect_err("needs an error when passing in block data"); + + assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams); + + let get_block_template_sync_error = get_block_template_rpc + .get_block_template(Some( + get_block_template_rpcs::types::get_block_template_opts::JsonParameters { + longpollid: Some("".to_string()), + ..Default::default() + }, + )) + .await + .expect_err("needs an error when using unsupported option"); + + assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams); } #[cfg(feature = "getblocktemplate-rpcs")] diff --git a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs index 318330531..953bfeff3 100644 --- a/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs +++ b/zebrad/tests/common/get_block_template_rpcs/get_block_template.rs @@ -63,7 +63,11 @@ pub(crate) async fn run() -> Result<()> { with a mempool that is likely empty...", ); let getblocktemplate_response = RPCRequestClient::new(rpc_address) - .call("getblocktemplate", "[]".to_string()) + .call( + "getblocktemplate", + // test that unknown capabilities are parsed as valid input + "[{\"capabilities\": [\"generation\"]}]".to_string(), + ) .await?; let is_response_success = getblocktemplate_response.status().is_success();