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

fix(rpc): Return detailed errors to the RPC client when a block proposal fails #5993

Merged
merged 2 commits into from
Jan 19, 2023
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use zebra_chain::{
block::{
self,
merkle::{self, AuthDataRoot},
ChainHistoryBlockTxAuthCommitmentHash, Height,
Block, ChainHistoryBlockTxAuthCommitmentHash, Height,
},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
Expand Down Expand Up @@ -108,8 +108,18 @@ where
+ Sync
+ 'static,
{
let Ok(block) = block_proposal_bytes.zcash_deserialize_into::<block::Block>() else {
return Ok(ProposalRejectReason::Rejected.into())
let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
Ok(block) => block,
Err(parse_error) => {
tracing::info!(
?parse_error,
"error response from block parser in CheckProposal request"
);

return Ok(
ProposalResponse::rejected("invalid proposal format", parse_error.into()).into(),
);
}
};

let chain_verifier_response = chain_verifier
Expand All @@ -127,11 +137,11 @@ where
.map(|_hash| ProposalResponse::Valid)
.unwrap_or_else(|verify_chain_error| {
tracing::info!(
verify_chain_error,
"Got error response from chain_verifier CheckProposal request"
?verify_chain_error,
"error response from chain_verifier in CheckProposal request"
);

ProposalRejectReason::Rejected.into()
ProposalResponse::rejected("invalid proposal", verify_chain_error)
})
.into())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub mod parameters;
pub mod proposal;

pub use parameters::{GetBlockTemplateCapability, GetBlockTemplateRequestMode, JsonParameters};
pub use proposal::{proposal_block_from_template, ProposalRejectReason, ProposalResponse};
pub use proposal::{proposal_block_from_template, ProposalResponse};

/// A serialized `getblocktemplate` RPC response in template mode.
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
//!
//! `ProposalResponse` is the output of the `getblocktemplate` RPC method in 'proposal' mode.

use std::{num::ParseIntError, str::FromStr, sync::Arc};
use std::{error::Error, num::ParseIntError, str::FromStr, sync::Arc};

use zebra_chain::{
block::{self, Block, Height},
serialization::{DateTime32, SerializationError, ZcashDeserializeInto},
work::equihash::Solution,
};
use zebra_node_services::BoxError;

use crate::methods::{
get_block_template_rpcs::types::{
Expand All @@ -18,47 +19,46 @@ use crate::methods::{
GetBlockHash,
};

/// Error response to a `getblocktemplate` RPC request in proposal mode.
///
/// See <https://en.bitcoin.it/wiki/BIP_0022#Appendix:_Example_Rejection_Reasons>
#[derive(Copy, Clone, 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>
/// <https://en.bitcoin.it/wiki/BIP_0022#Appendix:_Example_Rejection_Reasons>
///
/// Note:
/// The error response specification at <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
/// seems to have a copy-paste issue, or it is under-specified. We follow the `zcashd`
/// implementation instead, which returns a single raw string.
#[derive(Clone, 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 rejected as invalid.
/// Contains the reason that the proposal was invalid.
///
/// TODO: turn this into a typed error enum?
Rejected(String),
teor2345 marked this conversation as resolved.
Show resolved Hide resolved

/// 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 ProposalResponse {
/// Return a rejected response containing an error kind and detailed error info.
pub fn rejected<S: ToString>(kind: S, error: BoxError) -> Self {
let kind = kind.to_string();

// Pretty-print the detailed error for now
ProposalResponse::Rejected(format!("{kind}: {error:#?}"))
}

/// Return a rejected response containing just the detailed error information.
pub fn error(error: BoxError) -> Self {
// Pretty-print the detailed error for now
ProposalResponse::Rejected(format!("{error:#?}"))
}
}

impl From<ProposalRejectReason> for Response {
fn from(error_response: ProposalRejectReason) -> Self {
Self::ProposalMode(ProposalResponse::from(error_response))
impl<E: Error + Send + Sync + 'static> From<E> for ProposalResponse {
fn from(error: E) -> Self {
Self::error(error.into())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}
"invalid proposal format: Io(\n Error {\n kind: UnexpectedEof,\n message: \"failed to fill whole buffer\",\n },\n)"
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template
---
{
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}
"invalid proposal format: Io(\n Error {\n kind: UnexpectedEof,\n message: \"failed to fill whole buffer\",\n },\n)"
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> {
"got getblocktemplate proposal response"
);

if let ProposalResponse::ErrorResponse { reject_reason, .. } = json_result {
if let ProposalResponse::Rejected(reject_reason) = json_result {
Err(eyre!(
"unsuccessful block proposal validation, reason: {reject_reason:?}"
))?;
Expand Down