change(test): Create test harness for calling getblocktemplate in proposal mode, but don't use it yet (#5884)
* adds ValidateBlock request to state * adds `Request` enum in block verifier skips solution check for BlockProposal requests calls CheckBlockValidity instead of Commit block for BlockProposal requests * uses new Request in references to chain verifier * adds getblocktemplate proposal mode response type * makes getblocktemplate-rpcs feature in zebra-consensus select getblocktemplate-rpcs in zebra-state * Adds PR review revisions * adds info log in CheckBlockProposalValidity * Reverts replacement of match statement * adds `GetBlockTemplate::capabilities` fn * conditions calling checkpoint verifier on !request.is_proposal * updates references to validate_and_commit_non_finalized * adds snapshot test, updates test vectors * adds `should_count_metrics` to NonFinalizedState * Returns an error from chain verifier for block proposal requests below checkpoint height adds feature flags * adds "proposal" to GET_BLOCK_TEMPLATE_CAPABILITIES_FIELD * adds back block::Request to zebra-consensus lib * updates snapshots * Removes unnecessary network arg * skips req in tracing intstrument for read state * Moves out block proposal validation to its own fn * corrects `difficulty_threshold_is_valid` docs adds/fixes some comments, adds TODOs general cleanup from a self-review. * Update zebra-state/src/service.rs * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Update zebra-rpc/src/methods/get_block_template_rpcs.rs Co-authored-by: teor <teor@riseup.net> * check best chain tip * Update zebra-state/src/service.rs Co-authored-by: teor <teor@riseup.net> * Applies cleanup suggestions from code review * updates gbt acceptance test to make a block proposal * fixes json parsing mistake * adds retries * returns reject reason if there are no retries left * moves result deserialization to RPCRequestClient method, adds docs, moves jsonrpc_core to dev-dependencies * moves sleep(EXPECTED_TX_TIME) out of loop * updates/adds info logs in retry loop * Revert "moves sleep(EXPECTED_TX_TIME) out of loop" This reverts commit f7f0926f4050519687a79afc16656c3f345c004b. * adds `allow(dead_code)` * tests with curtime, mintime, & maxtime * Fixes doc comment * Logs error responses from chain_verifier CheckProposal requests * Removes retry loop, adds num_txs log * removes verbose info log * sorts mempool_txs before generating merkle root * Make imports conditional on a feature * Disable new CI tests until bugs are fixed Co-authored-by: teor <teor@riseup.net> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
402ed3eaac
commit
b0ba920a4f
|
|
@ -5653,6 +5653,7 @@ dependencies = [
|
||||||
"hyper",
|
"hyper",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"inferno",
|
"inferno",
|
||||||
|
"jsonrpc-core",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"metrics",
|
"metrics",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,12 @@ impl Solution {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
/// Returns a [`Solution`] of `[0; SOLUTION_SIZE]` to be used in block proposals.
|
||||||
|
pub fn for_proposal() -> Self {
|
||||||
|
Self([0; SOLUTION_SIZE])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<Solution> for Solution {
|
impl PartialEq<Solution> for Solution {
|
||||||
|
|
|
||||||
|
|
@ -1193,7 +1193,7 @@ pub enum GetBlock {
|
||||||
///
|
///
|
||||||
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
|
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct GetBlockHash(#[serde(with = "hex")] block::Hash);
|
pub struct GetBlockHash(#[serde(with = "hex")] pub block::Hash);
|
||||||
|
|
||||||
/// Response to a `z_gettreestate` RPC request.
|
/// Response to a `z_gettreestate` RPC request.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ where
|
||||||
+ Sync
|
+ Sync
|
||||||
+ 'static,
|
+ 'static,
|
||||||
{
|
{
|
||||||
let Ok(block) = block_proposal_bytes.zcash_deserialize_into() else {
|
let Ok(block) = block_proposal_bytes.zcash_deserialize_into::<block::Block>() else {
|
||||||
return Ok(ProposalRejectReason::Rejected.into())
|
return Ok(ProposalRejectReason::Rejected.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -125,7 +125,14 @@ where
|
||||||
|
|
||||||
Ok(chain_verifier_response
|
Ok(chain_verifier_response
|
||||||
.map(|_hash| ProposalResponse::Valid)
|
.map(|_hash| ProposalResponse::Valid)
|
||||||
.unwrap_or_else(|_| ProposalRejectReason::Rejected.into())
|
.unwrap_or_else(|verify_chain_error| {
|
||||||
|
tracing::info!(
|
||||||
|
verify_chain_error,
|
||||||
|
"Got error response from chain_verifier CheckProposal request"
|
||||||
|
);
|
||||||
|
|
||||||
|
ProposalRejectReason::Rejected.into()
|
||||||
|
})
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
//! The `GetBlockTempate` type is the output of the `getblocktemplate` RPC method.
|
//! The `GetBlockTempate` type is the output of the `getblocktemplate` RPC method in the
|
||||||
|
//! default 'template' mode. See [`ProposalResponse`] for the output in 'proposal' mode.
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount,
|
amount,
|
||||||
|
|
@ -27,8 +28,10 @@ use crate::methods::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod parameters;
|
pub mod parameters;
|
||||||
|
pub mod proposal;
|
||||||
|
|
||||||
pub use parameters::*;
|
pub use parameters::*;
|
||||||
|
pub use proposal::*;
|
||||||
|
|
||||||
/// A serialized `getblocktemplate` RPC response in template mode.
|
/// A serialized `getblocktemplate` RPC response in template mode.
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
|
@ -283,33 +286,6 @@ 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 proposal rejected as invalid.
|
|
||||||
Rejected,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Response to a `getblocktemplate` RPC request in proposal mode.
|
|
||||||
///
|
|
||||||
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
|
||||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
||||||
#[serde(untagged, rename_all = "kebab-case")]
|
|
||||||
pub enum ProposalResponse {
|
|
||||||
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
|
|
||||||
ErrorResponse {
|
|
||||||
/// Reason the proposal was invalid as-is.
|
|
||||||
reject_reason: ProposalRejectReason,
|
|
||||||
|
|
||||||
/// The getblocktemplate RPC capabilities supported by Zebra.
|
|
||||||
capabilities: Vec<String>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Block proposal was successfully validated, returns null.
|
|
||||||
Valid,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
/// A `getblocktemplate` RPC response.
|
/// A `getblocktemplate` RPC response.
|
||||||
|
|
@ -320,30 +296,3 @@ pub enum Response {
|
||||||
/// `getblocktemplate` RPC request in proposal mode.
|
/// `getblocktemplate` RPC request in proposal mode.
|
||||||
ProposalMode(ProposalResponse),
|
ProposalMode(ProposalResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ProposalRejectReason> for ProposalResponse {
|
|
||||||
fn from(reject_reason: ProposalRejectReason) -> Self {
|
|
||||||
Self::ErrorResponse {
|
|
||||||
reject_reason,
|
|
||||||
capabilities: GetBlockTemplate::capabilities(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ProposalRejectReason> for Response {
|
|
||||||
fn from(error_response: ProposalRejectReason) -> Self {
|
|
||||||
Self::ProposalMode(ProposalResponse::from(error_response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ProposalResponse> for Response {
|
|
||||||
fn from(proposal_response: ProposalResponse) -> Self {
|
|
||||||
Self::ProposalMode(proposal_response)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<GetBlockTemplate> for Response {
|
|
||||||
fn from(template: GetBlockTemplate) -> Self {
|
|
||||||
Self::TemplateMode(Box::new(template))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! `ProposalResponse` is the output of the `getblocktemplate` RPC method in 'proposal' mode.
|
||||||
|
|
||||||
|
use super::{GetBlockTemplate, Response};
|
||||||
|
|
||||||
|
/// Error response to a `getblocktemplate` RPC request in proposal mode.
|
||||||
|
///
|
||||||
|
/// See <https://en.bitcoin.it/wiki/BIP_0022#Appendix:_Example_Rejection_Reasons>
|
||||||
|
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case")]
|
||||||
|
pub enum ProposalRejectReason {
|
||||||
|
/// Block proposal rejected as invalid.
|
||||||
|
Rejected,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Response to a `getblocktemplate` RPC request in proposal mode.
|
||||||
|
///
|
||||||
|
/// See <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
|
||||||
|
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[serde(untagged, rename_all = "kebab-case")]
|
||||||
|
pub enum ProposalResponse {
|
||||||
|
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`.
|
||||||
|
ErrorResponse {
|
||||||
|
/// Reason the proposal was invalid as-is.
|
||||||
|
reject_reason: ProposalRejectReason,
|
||||||
|
|
||||||
|
/// The getblocktemplate RPC capabilities supported by Zebra.
|
||||||
|
capabilities: Vec<String>,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Block proposal was successfully validated, returns null.
|
||||||
|
Valid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProposalRejectReason> for ProposalResponse {
|
||||||
|
fn from(reject_reason: ProposalRejectReason) -> Self {
|
||||||
|
Self::ErrorResponse {
|
||||||
|
reject_reason,
|
||||||
|
capabilities: GetBlockTemplate::capabilities(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProposalRejectReason> for Response {
|
||||||
|
fn from(error_response: ProposalRejectReason) -> Self {
|
||||||
|
Self::ProposalMode(ProposalResponse::from(error_response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProposalResponse> for Response {
|
||||||
|
fn from(proposal_response: ProposalResponse) -> Self {
|
||||||
|
Self::ProposalMode(proposal_response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GetBlockTemplate> for Response {
|
||||||
|
fn from(template: GetBlockTemplate) -> Self {
|
||||||
|
Self::TemplateMode(Box::new(template))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ where
|
||||||
{
|
{
|
||||||
/// The hex-encoded serialized data for this transaction.
|
/// The hex-encoded serialized data for this transaction.
|
||||||
#[serde(with = "hex")]
|
#[serde(with = "hex")]
|
||||||
pub(crate) data: SerializedTransaction,
|
pub data: SerializedTransaction,
|
||||||
|
|
||||||
/// The transaction ID of this transaction.
|
/// The transaction ID of this transaction.
|
||||||
#[serde(with = "hex")]
|
#[serde(with = "hex")]
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ tonic-build = { version = "0.8.0", optional = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
abscissa_core = { version = "0.5", features = ["testing"] }
|
abscissa_core = { version = "0.5", features = ["testing"] }
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
|
jsonrpc-core = "18.0.0"
|
||||||
once_cell = "1.17.0"
|
once_cell = "1.17.0"
|
||||||
regex = "1.7.1"
|
regex = "1.7.1"
|
||||||
semver = "1.0.16"
|
semver = "1.0.16"
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,23 @@
|
||||||
//!
|
//!
|
||||||
//! After finishing the sync, it will call getblocktemplate.
|
//! After finishing the sync, it will call getblocktemplate.
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use color_eyre::eyre::{eyre, Context, Result};
|
use color_eyre::eyre::{eyre, Context, Result};
|
||||||
|
|
||||||
use zebra_chain::parameters::Network;
|
use zebra_chain::{
|
||||||
|
block::{self, Block, Height},
|
||||||
|
parameters::Network,
|
||||||
|
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
||||||
|
work::equihash::Solution,
|
||||||
|
};
|
||||||
|
use zebra_rpc::methods::{
|
||||||
|
get_block_template_rpcs::{
|
||||||
|
get_block_template::{GetBlockTemplate, ProposalResponse},
|
||||||
|
types::default_roots::DefaultRoots,
|
||||||
|
},
|
||||||
|
GetBlockHash,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc},
|
launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc},
|
||||||
|
|
@ -58,11 +70,13 @@ pub(crate) async fn run() -> Result<()> {
|
||||||
true,
|
true,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let client = RPCRequestClient::new(rpc_address);
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"calling getblocktemplate RPC method at {rpc_address}, \
|
"calling getblocktemplate RPC method at {rpc_address}, \
|
||||||
with a mempool that is likely empty...",
|
with a mempool that is likely empty...",
|
||||||
);
|
);
|
||||||
let getblocktemplate_response = RPCRequestClient::new(rpc_address)
|
let getblocktemplate_response = client
|
||||||
.call(
|
.call(
|
||||||
"getblocktemplate",
|
"getblocktemplate",
|
||||||
// test that unknown capabilities are parsed as valid input
|
// test that unknown capabilities are parsed as valid input
|
||||||
|
|
@ -84,25 +98,21 @@ pub(crate) async fn run() -> Result<()> {
|
||||||
"waiting {EXPECTED_MEMPOOL_TRANSACTION_TIME:?} for the mempool \
|
"waiting {EXPECTED_MEMPOOL_TRANSACTION_TIME:?} for the mempool \
|
||||||
to download and verify some transactions...",
|
to download and verify some transactions...",
|
||||||
);
|
);
|
||||||
|
|
||||||
tokio::time::sleep(EXPECTED_MEMPOOL_TRANSACTION_TIME).await;
|
tokio::time::sleep(EXPECTED_MEMPOOL_TRANSACTION_TIME).await;
|
||||||
|
|
||||||
|
/* TODO: activate this test after #5925 and #5953 have merged,
|
||||||
|
and we've checked for any other bugs using #5944.
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"calling getblocktemplate RPC method at {rpc_address}, \
|
"calling getblocktemplate RPC method at {rpc_address}, \
|
||||||
with a mempool that likely has transactions...",
|
with a mempool that likely has transactions and attempting \
|
||||||
);
|
to validate response result as a block proposal",
|
||||||
let getblocktemplate_response = RPCRequestClient::new(rpc_address)
|
|
||||||
.call("getblocktemplate", "[]".to_string())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let is_response_success = getblocktemplate_response.status().is_success();
|
|
||||||
let response_text = getblocktemplate_response.text().await?;
|
|
||||||
|
|
||||||
tracing::info!(
|
|
||||||
response_text,
|
|
||||||
"got getblocktemplate response, hopefully with transactions"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(is_response_success);
|
try_validate_block_template(&client)
|
||||||
|
.await
|
||||||
|
.expect("block proposal validation failed");
|
||||||
|
*/
|
||||||
|
|
||||||
zebrad.kill(false)?;
|
zebrad.kill(false)?;
|
||||||
|
|
||||||
|
|
@ -112,7 +122,112 @@ pub(crate) async fn run() -> Result<()> {
|
||||||
// [Note on port conflict](#Note on port conflict)
|
// [Note on port conflict](#Note on port conflict)
|
||||||
output
|
output
|
||||||
.assert_was_killed()
|
.assert_was_killed()
|
||||||
.wrap_err("Possible port conflict. Are there other acceptance tests running?")?;
|
.wrap_err("Possible port conflict. Are there other acceptance tests running?")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts an [`RPCRequestClient`], calls getblocktemplate in template mode,
|
||||||
|
/// deserializes and transforms the block template in the response into block proposal data,
|
||||||
|
/// then calls getblocktemplate RPC in proposal mode with the serialized and hex-encoded data.
|
||||||
|
///
|
||||||
|
/// Returns an error if it fails to transform template to block proposal or serialize the block proposal
|
||||||
|
/// Returns `Ok(())` if the block proposal is valid or an error with the reject-reason if the result is
|
||||||
|
/// an `ErrorResponse`.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// If an RPC call returns a failure
|
||||||
|
/// If the response result cannot be deserialized to `GetBlockTemplate` in 'template' mode
|
||||||
|
/// or `ProposalResponse` in 'proposal' mode.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> {
|
||||||
|
let response_json_result = client
|
||||||
|
.json_result_from_call("getblocktemplate", "[]".to_string())
|
||||||
|
.await
|
||||||
|
.expect("response should be success output with with a serialized `GetBlockTemplate`");
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?response_json_result,
|
||||||
|
"got getblocktemplate response, hopefully with transactions"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Propose a new block with an empty solution and nonce field
|
||||||
|
tracing::info!("calling getblocktemplate with a block proposal...",);
|
||||||
|
|
||||||
|
for proposal_block in proposal_block_from_template(response_json_result)? {
|
||||||
|
let raw_proposal_block = hex::encode(proposal_block.zcash_serialize_to_vec()?);
|
||||||
|
|
||||||
|
let json_result = client
|
||||||
|
.json_result_from_call(
|
||||||
|
"getblocktemplate",
|
||||||
|
format!(r#"[{{"mode":"proposal","data":"{raw_proposal_block}"}}]"#),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("response should be success output with with a serialized `ProposalResponse`");
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
?json_result,
|
||||||
|
?proposal_block.header.time,
|
||||||
|
"got getblocktemplate proposal response"
|
||||||
|
);
|
||||||
|
|
||||||
|
if let ProposalResponse::ErrorResponse { reject_reason, .. } = json_result {
|
||||||
|
Err(eyre!(
|
||||||
|
"unsuccessful block proposal validation, reason: {reject_reason:?}"
|
||||||
|
))?;
|
||||||
|
} else {
|
||||||
|
assert_eq!(ProposalResponse::Valid, json_result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Make block proposals from [`GetBlockTemplate`]
|
||||||
|
///
|
||||||
|
/// Returns an array of 3 block proposals using `curtime`, `mintime`, and `maxtime`
|
||||||
|
/// for their `block.header.time` fields.
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn proposal_block_from_template(
|
||||||
|
GetBlockTemplate {
|
||||||
|
version,
|
||||||
|
height,
|
||||||
|
previous_block_hash: GetBlockHash(previous_block_hash),
|
||||||
|
default_roots:
|
||||||
|
DefaultRoots {
|
||||||
|
merkle_root,
|
||||||
|
block_commitments_hash,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
bits: difficulty_threshold,
|
||||||
|
coinbase_txn,
|
||||||
|
transactions: tx_templates,
|
||||||
|
cur_time,
|
||||||
|
min_time,
|
||||||
|
max_time,
|
||||||
|
..
|
||||||
|
}: GetBlockTemplate,
|
||||||
|
) -> Result<[Block; 3]> {
|
||||||
|
if Height(height) > Height::MAX {
|
||||||
|
Err(eyre!("height field must be lower than Height::MAX"))?;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut transactions = vec![coinbase_txn.data.as_ref().zcash_deserialize_into()?];
|
||||||
|
|
||||||
|
for tx_template in tx_templates {
|
||||||
|
transactions.push(tx_template.data.as_ref().zcash_deserialize_into()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok([cur_time, min_time, max_time].map(|time| Block {
|
||||||
|
header: Arc::new(block::Header {
|
||||||
|
version,
|
||||||
|
previous_block_hash,
|
||||||
|
merkle_root,
|
||||||
|
commitment_bytes: block_commitments_hash.into(),
|
||||||
|
time: time.into(),
|
||||||
|
difficulty_threshold,
|
||||||
|
nonce: [0; 32],
|
||||||
|
solution: Solution::for_proposal(),
|
||||||
|
}),
|
||||||
|
transactions: transactions.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ use std::net::SocketAddr;
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
use color_eyre::{eyre::eyre, Result};
|
||||||
|
|
||||||
/// An http client for making Json-RPC requests
|
/// An http client for making Json-RPC requests
|
||||||
pub struct RPCRequestClient {
|
pub struct RPCRequestClient {
|
||||||
client: Client,
|
client: Client,
|
||||||
|
|
@ -44,4 +47,33 @@ impl RPCRequestClient {
|
||||||
) -> reqwest::Result<String> {
|
) -> reqwest::Result<String> {
|
||||||
self.call(method, params).await?.text().await
|
self.call(method, params).await?.text().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds an RPC request, awaits its response, and attempts to deserialize
|
||||||
|
/// it to the expected result type.
|
||||||
|
///
|
||||||
|
/// Returns Ok with json result from response if successful.
|
||||||
|
/// Returns an error if the call or result deserialization fail.
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
pub async fn json_result_from_call<T: serde::de::DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
method: &'static str,
|
||||||
|
params: impl Into<String>,
|
||||||
|
) -> Result<T> {
|
||||||
|
Self::json_result_from_response_text(&self.text_from_call(method, params).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Accepts response text from an RPC call
|
||||||
|
/// Returns `Ok` with a deserialized `result` value in the expected type, or an error report.
|
||||||
|
#[cfg(feature = "getblocktemplate-rpcs")]
|
||||||
|
fn json_result_from_response_text<T: serde::de::DeserializeOwned>(
|
||||||
|
response_text: &str,
|
||||||
|
) -> Result<T> {
|
||||||
|
use jsonrpc_core::Output;
|
||||||
|
|
||||||
|
let output: Output = serde_json::from_str(response_text)?;
|
||||||
|
match output {
|
||||||
|
Output::Success(success) => Ok(serde_json::from_value(success.result)?),
|
||||||
|
Output::Failure(failure) => Err(eyre!("RPC call failed with: {failure:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue