Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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,6 +10,7 @@ use crate::error::DapiError;
use crate::services::PlatformServiceImpl;
use crate::services::platform_service::TenderdashStatus;
use crate::services::platform_service::error_mapping::decode_consensus_error;
use crate::services::platform_service::error_mapping::map_tenderdash_message;
use base64::prelude::*;
use dapi_grpc::platform::v0::{BroadcastStateTransitionRequest, BroadcastStateTransitionResponse};
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -217,34 +218,11 @@ fn map_broadcast_error(code: u32, error_message: &str, info: Option<&str>) -> Da
code,
error_message
);
if error_message == "tx already exists in cache" {
return DapiError::AlreadyExists(error_message.to_string());
}

if error_message.starts_with("Tx too large.") {
let message = error_message.replace("Tx too large. ", "");
return DapiError::InvalidArgument(
"state transition is too large. ".to_string() + &message,
);
}

if error_message.starts_with("mempool is full") {
return DapiError::ResourceExhausted(error_message.to_string());
}

if error_message.contains("context deadline exceeded") {
return DapiError::Timeout("broadcasting state transition is timed out".to_string());
}

if error_message.contains("too_many_requests") {
return DapiError::ResourceExhausted(
"tenderdash is not responding: too many requests".to_string(),
);
if let Some(mapped_error) = map_tenderdash_message(error_message) {
return mapped_error;
}

if error_message.starts_with("broadcast confirmation not received:") {
return DapiError::Timeout(error_message.to_string());
}
let consensus_error = info.and_then(|x| decode_consensus_error(x.to_string()));
let message = if error_message.is_empty() {
None
Expand Down
59 changes: 56 additions & 3 deletions packages/rs-dapi/src/services/platform_service/error_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ use dpp::{consensus::ConsensusError, serialization::PlatformDeserializable};
use std::{fmt::Debug, str::FromStr};
use tonic::{Code, metadata::MetadataValue};

use crate::DapiError;

#[derive(Clone, serde::Serialize)]
pub struct TenderdashStatus {
pub code: i64,
Expand Down Expand Up @@ -39,6 +41,14 @@ impl TenderdashStatus {
let status_code = self.grpc_code();
let status_message = self.grpc_message();

// check if we can map to a DapiError first
if let Some(dapi_error) = map_tenderdash_message(&status_message) {
// avoid infinite recursion
if !matches!(dapi_error, DapiError::TenderdashClientError(_)) {
return dapi_error.to_status();
}
}

let mut status: tonic::Status = tonic::Status::new(status_code, status_message);

self.write_grpc_metadata(status.metadata_mut());
Expand Down Expand Up @@ -273,9 +283,17 @@ impl From<serde_json::Value> for TenderdashStatus {
tracing::debug!("Tenderdash error missing 'code' field, defaulting to 0");
0
});
let message = object
.get("message")
.and_then(|m| m.as_str())
let raw_message = object.get("message").and_then(|m| m.as_str());
// empty message or "Internal error" is not very informative, so we try to check `data` field
let message =
if raw_message.is_none_or(|m| m.trim().eq_ignore_ascii_case("Internal error")) {
object
.get("data")
.and_then(|d| d.as_str())
.filter(|s| s.is_ascii())
} else {
raw_message
}
.map(|s| s.to_string());

// info contains additional error details, possibly including consensus error
Expand All @@ -300,6 +318,41 @@ impl From<serde_json::Value> for TenderdashStatus {
}
}

// Map some common Tenderdash error messages to DapiError variants
pub(super) fn map_tenderdash_message(message: &str) -> Option<DapiError> {
let msg = message.trim().to_lowercase();
if msg == "tx already exists in cache" {
return Some(DapiError::AlreadyExists(msg.to_string()));
}

if msg.starts_with("tx too large.") {
let message = msg.replace("tx too large.", "").trim().to_string();
return Some(DapiError::InvalidArgument(
"state transition is too large. ".to_string() + &message,
));
}

if msg.starts_with("mempool is full") {
return Some(DapiError::ResourceExhausted(msg.to_string()));
}

if msg.contains("context deadline exceeded") {
return Some(DapiError::Timeout(
"broadcasting state transition is timed out".to_string(),
));
}

if msg.contains("too_many_requests") {
return Some(DapiError::ResourceExhausted(
"tenderdash is not responding: too many requests".to_string(),
));
}

if msg.starts_with("broadcast confirmation not received:") {
return Some(DapiError::Timeout(msg.to_string()));
}
None
}
#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading