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

* Return detailed errors to the RPC client when a block proposal fails

* Change proposal reject type in acceptance test
This commit is contained in:
teor 2023-01-19 16:41:55 +10:00 committed by GitHub
parent de2ed812f6
commit b3a480757a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 49 additions and 49 deletions

View File

@ -10,7 +10,7 @@ use zebra_chain::{
block::{
self,
merkle::{self, AuthDataRoot},
ChainHistoryBlockTxAuthCommitmentHash, Height,
Block, ChainHistoryBlockTxAuthCommitmentHash, Height,
},
chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip,
@ -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
@ -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())
}

View File

@ -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)]

View File

@ -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::{
@ -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),
/// 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())
}
}

View File

@ -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)"

View File

@ -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)"

View File

@ -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:?}"
))?;