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