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::{ block::{
self, self,
merkle::{self, AuthDataRoot}, merkle::{self, AuthDataRoot},
ChainHistoryBlockTxAuthCommitmentHash, Height, Block, ChainHistoryBlockTxAuthCommitmentHash, Height,
}, },
chain_sync_status::ChainSyncStatus, chain_sync_status::ChainSyncStatus,
chain_tip::ChainTip, chain_tip::ChainTip,
@ -108,8 +108,18 @@ where
+ Sync + Sync
+ 'static, + 'static,
{ {
let Ok(block) = block_proposal_bytes.zcash_deserialize_into::<block::Block>() else { let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
return Ok(ProposalRejectReason::Rejected.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 let chain_verifier_response = chain_verifier
@ -127,11 +137,11 @@ where
.map(|_hash| ProposalResponse::Valid) .map(|_hash| ProposalResponse::Valid)
.unwrap_or_else(|verify_chain_error| { .unwrap_or_else(|verify_chain_error| {
tracing::info!( tracing::info!(
verify_chain_error, ?verify_chain_error,
"Got error response from chain_verifier CheckProposal request" "error response from chain_verifier in CheckProposal request"
); );
ProposalRejectReason::Rejected.into() ProposalResponse::rejected("invalid proposal", verify_chain_error)
}) })
.into()) .into())
} }

View File

@ -31,7 +31,7 @@ pub mod parameters;
pub mod proposal; pub mod proposal;
pub use parameters::{GetBlockTemplateCapability, GetBlockTemplateRequestMode, JsonParameters}; 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. /// 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)]

View File

@ -2,13 +2,14 @@
//! //!
//! `ProposalResponse` is the output of the `getblocktemplate` RPC method in 'proposal' mode. //! `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::{ use zebra_chain::{
block::{self, Block, Height}, block::{self, Block, Height},
serialization::{DateTime32, SerializationError, ZcashDeserializeInto}, serialization::{DateTime32, SerializationError, ZcashDeserializeInto},
work::equihash::Solution, work::equihash::Solution,
}; };
use zebra_node_services::BoxError;
use crate::methods::{ use crate::methods::{
get_block_template_rpcs::types::{ get_block_template_rpcs::types::{
@ -18,47 +19,46 @@ use crate::methods::{
GetBlockHash, 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. /// 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)] #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged, rename_all = "kebab-case")] #[serde(untagged, rename_all = "kebab-case")]
pub enum ProposalResponse { pub enum ProposalResponse {
/// Block proposal was rejected as invalid, returns `reject-reason` and server `capabilities`. /// Block proposal was rejected as invalid.
ErrorResponse { /// Contains the reason that the proposal was invalid.
/// Reason the proposal was invalid as-is. ///
reject_reason: ProposalRejectReason, /// TODO: turn this into a typed error enum?
Rejected(String),
/// The getblocktemplate RPC capabilities supported by Zebra.
capabilities: Vec<String>,
},
/// Block proposal was successfully validated, returns null. /// Block proposal was successfully validated, returns null.
Valid, Valid,
} }
impl From<ProposalRejectReason> for ProposalResponse { impl ProposalResponse {
fn from(reject_reason: ProposalRejectReason) -> Self { /// Return a rejected response containing an error kind and detailed error info.
Self::ErrorResponse { pub fn rejected<S: ToString>(kind: S, error: BoxError) -> Self {
reject_reason, let kind = kind.to_string();
capabilities: GetBlockTemplate::capabilities(),
} // 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 { impl<E: Error + Send + Sync + 'static> From<E> for ProposalResponse {
fn from(error_response: ProposalRejectReason) -> Self { fn from(error: E) -> Self {
Self::ProposalMode(ProposalResponse::from(error_response)) Self::error(error.into())
} }
} }

View File

@ -2,9 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template expression: block_template
--- ---
{ "invalid proposal format: Io(\n Error {\n kind: UnexpectedEof,\n message: \"failed to fill whole buffer\",\n },\n)"
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}

View File

@ -2,9 +2,4 @@
source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs source: zebra-rpc/src/methods/tests/snapshot/get_block_template_rpcs.rs
expression: block_template expression: block_template
--- ---
{ "invalid proposal format: Io(\n Error {\n kind: UnexpectedEof,\n message: \"failed to fill whole buffer\",\n },\n)"
"reject_reason": "rejected",
"capabilities": [
"proposal"
]
}

View File

@ -167,7 +167,7 @@ async fn try_validate_block_template(client: &RPCRequestClient) -> Result<()> {
"got getblocktemplate proposal response" "got getblocktemplate proposal response"
); );
if let ProposalResponse::ErrorResponse { reject_reason, .. } = json_result { if let ProposalResponse::Rejected(reject_reason) = json_result {
Err(eyre!( Err(eyre!(
"unsuccessful block proposal validation, reason: {reject_reason:?}" "unsuccessful block proposal validation, reason: {reject_reason:?}"
))?; ))?;