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:
parent
de2ed812f6
commit
b3a480757a
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
|
|
|||
|
|
@ -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:?}"
|
||||
))?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue