Skip to content

Commit

Permalink
refactor(jsonrpc): Structured errors from other JSON-RPC methods (#4213)
Browse files Browse the repository at this point in the history
* `status`
* `health`
* `EXPERIMENTAL_changes_in_block`
* `EXPERIMENTAL_changes`
* `EXPERIMENTAL_light_client_proof`
* `EXPERIMENTAL_validators_ordered`
* `light_client_proof`
  • Loading branch information
khorolets authored Apr 19, 2021
1 parent 06e8402 commit b335779
Show file tree
Hide file tree
Showing 24 changed files with 1,064 additions and 348 deletions.
194 changes: 186 additions & 8 deletions chain/client-primitives/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,16 +339,83 @@ pub struct Status {
pub is_health_check: bool,
}

#[derive(thiserror::Error, Debug)]
pub enum StatusError {
#[error("Node is syncing")]
NodeIsSyncing,
#[error("No blocks for {elapsed:?}")]
NoNewBlocks { elapsed: std::time::Duration },
#[error("Epoch Out Of Bounds {epoch_id:?}")]
EpochOutOfBounds { epoch_id: near_primitives::types::EpochId },
#[error("The node reached its limits. Try again later. More details: {error_message}")]
InternalError { error_message: String },
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")]
Unreachable { error_message: String },
}

impl From<near_chain_primitives::error::Error> for StatusError {
fn from(error: near_chain_primitives::error::Error) -> Self {
match error.kind() {
near_chain_primitives::error::ErrorKind::DBNotFoundErr(error_message)
| near_chain_primitives::error::ErrorKind::IOErr(error_message)
| near_chain_primitives::error::ErrorKind::ValidatorError(error_message) => {
Self::InternalError { error_message }
}
near_chain_primitives::error::ErrorKind::EpochOutOfBounds(epoch_id) => {
Self::EpochOutOfBounds { epoch_id }
}
_ => Self::Unreachable { error_message: error.to_string() },
}
}
}

impl Message for Status {
type Result = Result<StatusResponse, String>;
type Result = Result<StatusResponse, StatusError>;
}

pub struct GetNextLightClientBlock {
pub last_block_hash: CryptoHash,
}

#[derive(thiserror::Error, Debug)]
pub enum GetNextLightClientBlockError {
#[error("Internal error: {error_message}")]
InternalError { error_message: String },
#[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
UnknownBlock { error_message: String },
#[error("Epoch Out Of Bounds {epoch_id:?}")]
EpochOutOfBounds { epoch_id: near_primitives::types::EpochId },
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")]
Unreachable { error_message: String },
}

impl From<near_chain_primitives::error::Error> for GetNextLightClientBlockError {
fn from(error: near_chain_primitives::error::Error) -> Self {
match error.kind() {
near_chain_primitives::error::ErrorKind::DBNotFoundErr(error_message) => {
Self::UnknownBlock { error_message }
}
near_chain_primitives::error::ErrorKind::IOErr(error_message) => {
Self::InternalError { error_message }
}
near_chain_primitives::error::ErrorKind::EpochOutOfBounds(epoch_id) => {
Self::EpochOutOfBounds { epoch_id }
}
_ => Self::Unreachable { error_message: error.to_string() },
}
}
}

impl Message for GetNextLightClientBlock {
type Result = Result<Option<LightClientBlockView>, String>;
type Result = Result<Option<LightClientBlockView>, GetNextLightClientBlockError>;
}

pub struct GetNetworkInfo {}
Expand Down Expand Up @@ -482,45 +549,128 @@ pub struct GetValidatorOrdered {
}

impl Message for GetValidatorOrdered {
type Result = Result<Vec<ValidatorStakeView>, String>;
type Result = Result<Vec<ValidatorStakeView>, GetValidatorInfoError>;
}

pub struct GetStateChanges {
pub block_hash: CryptoHash,
pub state_changes_request: StateChangesRequestView,
}

#[derive(thiserror::Error, Debug)]
pub enum GetStateChangesError {
#[error("IO Error: {error_message}")]
IOError { error_message: String },
#[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
UnknownBlock { error_message: String },
#[error("There are no fully synchronized blocks yet")]
NotSyncedYet,
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")]
Unreachable { error_message: String },
}

impl From<near_chain_primitives::Error> for GetStateChangesError {
fn from(error: near_chain_primitives::Error) -> Self {
match error.kind() {
near_chain_primitives::ErrorKind::IOErr(error_message) => {
Self::IOError { error_message }
}
near_chain_primitives::ErrorKind::DBNotFoundErr(error_message) => {
Self::UnknownBlock { error_message }
}
_ => Self::Unreachable { error_message: error.to_string() },
}
}
}

impl Message for GetStateChanges {
type Result = Result<StateChangesView, String>;
type Result = Result<StateChangesView, GetStateChangesError>;
}

pub struct GetStateChangesInBlock {
pub block_hash: CryptoHash,
}

impl Message for GetStateChangesInBlock {
type Result = Result<StateChangesKindsView, String>;
type Result = Result<StateChangesKindsView, GetStateChangesError>;
}

pub struct GetStateChangesWithCauseInBlock {
pub block_hash: CryptoHash,
}

impl Message for GetStateChangesWithCauseInBlock {
type Result = Result<StateChangesView, String>;
type Result = Result<StateChangesView, GetStateChangesError>;
}

pub struct GetExecutionOutcome {
pub id: TransactionOrReceiptId,
}

#[derive(thiserror::Error, Debug)]
pub enum GetExecutionOutcomeError {
#[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
UnknownBlock { error_message: String },
#[error("Inconsistent state. Total number of shards is {number_or_shards} but the execution outcome is in shard {execution_outcome_shard_id}")]
InconsistentState {
number_or_shards: usize,
execution_outcome_shard_id: near_primitives::types::ShardId,
},
#[error("{transaction_or_receipt_id} has not been confirmed")]
NotConfirmed { transaction_or_receipt_id: near_primitives::hash::CryptoHash },
#[error("{transaction_or_receipt_id} does not exist")]
UnknownTransactionOrReceipt { transaction_or_receipt_id: near_primitives::hash::CryptoHash },
#[error("Node doesn't track the shard where {transaction_or_receipt_id} is executed")]
UnavailableShard {
transaction_or_receipt_id: near_primitives::hash::CryptoHash,
shard_id: near_primitives::types::ShardId,
},
#[error("Internal error: {error_message}")]
InternalError { error_message: String },
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")]
Unreachable { error_message: String },
}

impl From<TxStatusError> for GetExecutionOutcomeError {
fn from(error: TxStatusError) -> Self {
match error {
TxStatusError::ChainError(err) => {
Self::InternalError { error_message: err.to_string() }
}
_ => Self::Unreachable { error_message: format!("{:?}", error) },
}
}
}

impl From<near_chain_primitives::error::Error> for GetExecutionOutcomeError {
fn from(error: near_chain_primitives::error::Error) -> Self {
match error.kind() {
near_chain_primitives::ErrorKind::IOErr(error_message) => {
Self::InternalError { error_message }
}
near_chain_primitives::ErrorKind::DBNotFoundErr(error_message) => {
Self::UnknownBlock { error_message }
}
_ => Self::Unreachable { error_message: error.to_string() },
}
}
}

pub struct GetExecutionOutcomeResponse {
pub outcome_proof: ExecutionOutcomeWithIdView,
pub outcome_root_proof: MerklePath,
}

impl Message for GetExecutionOutcome {
type Result = Result<GetExecutionOutcomeResponse, String>;
type Result = Result<GetExecutionOutcomeResponse, GetExecutionOutcomeError>;
}

pub struct GetExecutionOutcomesForBlock {
Expand All @@ -541,8 +691,36 @@ pub struct GetBlockProofResponse {
pub proof: MerklePath,
}

#[derive(thiserror::Error, Debug)]
pub enum GetBlockProofError {
#[error("Block either has never been observed on the node or has been garbage collected: {error_message}")]
UnknownBlock { error_message: String },
#[error("Internal error: {error_message}")]
InternalError { error_message: String },
// NOTE: Currently, the underlying errors are too broad, and while we tried to handle
// expected cases, we cannot statically guarantee that no other errors will be returned
// in the future.
// TODO #3851: Remove this variant once we can exhaustively match all the underlying errors
#[error("It is a bug if you receive this error type, please, report this incident: https://github.com/near/nearcore/issues/new/choose. Details: {error_message}")]
Unreachable { error_message: String },
}

impl From<near_chain_primitives::error::Error> for GetBlockProofError {
fn from(error: near_chain_primitives::error::Error) -> Self {
match error.kind() {
near_chain_primitives::error::ErrorKind::DBNotFoundErr(error_message) => {
Self::UnknownBlock { error_message }
}
near_chain_primitives::error::ErrorKind::Other(error_message) => {
Self::InternalError { error_message }
}
err => Self::Unreachable { error_message: err.to_string() },
}
}
}

impl Message for GetBlockProof {
type Result = Result<GetBlockProofResponse, String>;
type Result = Result<GetBlockProofResponse, GetBlockProofError>;
}

pub struct GetReceipt {
Expand Down
26 changes: 9 additions & 17 deletions chain/client/src/client_actor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use crate::AdversarialControls;
use crate::StatusResponse;
use near_client_primitives::types::{
Error, GetNetworkInfo, NetworkInfoResponse, ShardSyncDownload, ShardSyncStatus, Status,
StatusSyncInfo, SyncStatus,
StatusError, StatusSyncInfo, SyncStatus,
};
use near_primitives::block_header::ApprovalType;

Expand Down Expand Up @@ -530,20 +530,16 @@ impl Handler<NetworkClientMessages> for ClientActor {
}

impl Handler<Status> for ClientActor {
type Result = Result<StatusResponse, String>;
type Result = Result<StatusResponse, StatusError>;

#[perf]
fn handle(&mut self, msg: Status, ctx: &mut Context<Self>) -> Self::Result {
#[cfg(feature = "delay_detector")]
let _d = DelayDetector::new("client status".to_string().into());
self.check_triggers(ctx);

let head = self.client.chain.head().map_err(|err| err.to_string())?;
let header = self
.client
.chain
.get_block_header(&head.last_block_hash)
.map_err(|err| err.to_string())?;
let head = self.client.chain.head()?;
let header = self.client.chain.get_block_header(&head.last_block_hash)?;
let latest_block_time = header.raw_timestamp().clone();
if msg.is_health_check {
let now = Utc::now();
Expand All @@ -556,31 +552,27 @@ impl Handler<Status> for ClientActor {
* STATUS_WAIT_TIME_MULTIPLIER,
)
{
return Err(format!("No blocks for {:?}.", elapsed));
return Err(StatusError::NoNewBlocks { elapsed });
}
}

if self.client.sync_status.is_syncing() {
return Err("Node is syncing.".to_string());
return Err(StatusError::NodeIsSyncing);
}
}
let validators = self
.client
.runtime_adapter
.get_epoch_block_producers_ordered(&head.epoch_id, &head.last_block_hash)
.map_err(|err| err.to_string())?
.get_epoch_block_producers_ordered(&head.epoch_id, &head.last_block_hash)?
.into_iter()
.map(|(validator_stake, is_slashed)| ValidatorInfo {
account_id: validator_stake.take_account_id(),
is_slashed,
})
.collect();

let protocol_version = self
.client
.runtime_adapter
.get_epoch_protocol_version(&head.epoch_id)
.map_err(|err| err.to_string())?;
let protocol_version =
self.client.runtime_adapter.get_epoch_protocol_version(&head.epoch_id)?;

let validator_account_id =
self.client.validator_signer.as_ref().map(|vs| vs.validator_id()).cloned();
Expand Down
Loading

0 comments on commit b335779

Please sign in to comment.