diff --git a/gstd/src/async_runtime/locks.rs b/gstd/src/async_runtime/locks.rs index eddbc7a83f2..196bd23b8a5 100644 --- a/gstd/src/async_runtime/locks.rs +++ b/gstd/src/async_runtime/locks.rs @@ -19,7 +19,7 @@ use crate::{ collections::BTreeMap, config::WaitType, - errors::{Error, Result}, + errors::{Error, Result, UsageError}, exec, sync::MutexId, BlockCount, BlockNumber, Config, MessageId, @@ -47,7 +47,7 @@ impl Lock { /// Wait for pub fn exactly(b: BlockCount) -> Result { if b == 0 { - return Err(Error::EmptyWaitDuration); + return Err(Error::Gstd(UsageError::EmptyWaitDuration)); } Ok(Self { @@ -59,7 +59,7 @@ impl Lock { /// Wait up to pub fn up_to(b: BlockCount) -> Result { if b == 0 { - return Err(Error::EmptyWaitDuration); + return Err(Error::Gstd(UsageError::EmptyWaitDuration)); } Ok(Self { diff --git a/gstd/src/common/errors.rs b/gstd/src/common/errors.rs index 1efe2a8aa8c..bac213f5375 100644 --- a/gstd/src/common/errors.rs +++ b/gstd/src/common/errors.rs @@ -22,7 +22,8 @@ //! Errors related to conversion, decoding, message status code, other internal //! errors. -use core::fmt; +use alloc::vec::Vec; +use core::{fmt, str}; use gcore::errors::Error as CoreError; pub use gcore::errors::*; @@ -33,26 +34,44 @@ pub type Result = core::result::Result; /// Common error type returned by API functions from other modules. #[derive(Clone, Debug, Eq, PartialEq)] pub enum Error { + /* Protocol under-hood errors */ /// [`gcore::errors::Error`] type. + /// + /// NOTE: this error could only be returned from syscalls. Core(CoreError), - /// Timeout reached while expecting for reply. - Timeout(u32, u32), + + /* API lib under-hood errors */ /// Conversion error. + /// + /// NOTE: this error returns from incorrect bytes conversion. Convert(&'static str), - /// Decoding error. + + /// `scale-codec` decoding error. + /// + /// NOTE: this error returns from APIs that return specific `Decode` types. Decode(scale_info::scale::Error), - /// Reply code returned by another program. - ReplyCode(ReplyCode), - /// This error occurs when providing zero duration to waiting functions - /// (e.g. see `exactly` and `up_to` functions in - /// [CodecMessageFuture](crate::msg::CodecMessageFuture)). - EmptyWaitDuration, - /// This error occurs when providing zero gas amount to system gas reserving - /// function (see - /// [Config::set_system_reserve](crate::Config::set_system_reserve)). - ZeroSystemReservationAmount, - /// This error occurs when providing zero duration to mutex lock function - ZeroMxLockDuration, + + /// Gstd API usage error. + /// + /// Note: this error returns from `gstd` APIs in case of invalid arguments. + Gstd(UsageError), + + /* Business logic errors */ + /// Received error reply while awaited response from another actor. + /// + /// NOTE: this error could only be returned from async messaging. + ErrorReply(ErrorReplyPayload, ErrorReplyReason), + + /// Received reply that couldn't be identified as successful or not + /// due to unsupported reply code. + /// + /// NOTE: this error could only be returned from async messaging. + UnsupportedReply(Vec), + + /// Timeout reached while expecting for reply. + /// + /// NOTE: this error could only be returned from async messaging. + Timeout(u32, u32), } impl Error { @@ -60,23 +79,32 @@ impl Error { pub fn timed_out(&self) -> bool { matches!(self, Error::Timeout(..)) } + + /// Check whether an error is [`Error::ErrorReply`] and return its str + /// representation. + pub fn error_reply_str(&self) -> Option<&str> { + if let Self::ErrorReply(payload, _) = self { + payload.try_as_str() + } else { + None + } + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Core(e) => fmt::Display::fmt(e, f), - Error::Timeout(expected, now) => { - write!(f, "Wait lock timeout at {expected}, now is {now}") - } Error::Convert(e) => write!(f, "Conversion error: {e:?}"), - Error::Decode(e) => write!(f, "Decoding codec bytes error: {e}"), - Error::ReplyCode(e) => write!(f, "Reply came with non success reply code {e:?}"), - Error::EmptyWaitDuration => write!(f, "Wait duration can not be zero."), - Error::ZeroSystemReservationAmount => { - write!(f, "System reservation amount can not be zero in config.") + Error::Decode(e) => write!(f, "Scale codec decoding error: {e}"), + Error::Gstd(e) => write!(f, "`Gstd` API error: {e:?}"), + Error::ErrorReply(err, reason) => write!(f, "Received reply '{err}' due to {reason:?}"), + Error::UnsupportedReply(payload) => { + write!(f, "Received unsupported reply '0x{}'", hex::encode(payload)) + } + Error::Timeout(expected, now) => { + write!(f, "Timeout has occurred: expected at {expected}, now {now}") } - Error::ZeroMxLockDuration => write!(f, "Mutex lock duration can not be zero."), } } } @@ -87,6 +115,71 @@ impl From for Error { } } +/// New-type representing error reply payload. Expected to be utf-8 string. +#[derive(Clone, Eq, PartialEq)] +pub struct ErrorReplyPayload(pub Vec); + +impl ErrorReplyPayload { + /// Represents self as utf-8 str, if possible. + pub fn try_as_str(&self) -> Option<&str> { + str::from_utf8(&self.0).ok() + } + + /// Similar to [`Self::try_as_str`], but panics in `None` case. + /// Preferable to use only for test purposes. + #[track_caller] + pub fn as_str(&self) -> &str { + str::from_utf8(&self.0).expect("Failed to create `str`") + } +} + +impl From> for ErrorReplyPayload { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl fmt::Debug for ErrorReplyPayload { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.try_as_str() + .map(|v| write!(f, "{v}")) + .unwrap_or_else(|| write!(f, "0x{}", hex::encode(&self.0))) + } +} + +impl fmt::Display for ErrorReplyPayload { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +/// Error type returned by gstd API while using invalid arguments. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum UsageError { + /// This error occurs when providing zero duration to waiting functions + /// (e.g. see `exactly` and `up_to` functions in + /// [`CodecMessageFuture`](crate::msg::CodecMessageFuture)). + EmptyWaitDuration, + /// This error occurs when providing zero gas amount to system gas reserving + /// function (see + /// [`Config::set_system_reserve`](crate::Config::set_system_reserve)). + ZeroSystemReservationAmount, + /// This error occurs when providing zero duration to mutex lock function + ZeroMxLockDuration, +} + +impl fmt::Display for UsageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + UsageError::EmptyWaitDuration => write!(f, "Wait duration can not be zero"), + UsageError::ZeroSystemReservationAmount => { + write!(f, "System reservation amount can not be zero in config") + } + UsageError::ZeroMxLockDuration => write!(f, "Mutex lock duration can not be zero"), + } + } +} + pub(crate) trait IntoResult { fn into_result(self) -> Result; } diff --git a/gstd/src/config.rs b/gstd/src/config.rs index 47c1cfee88a..4dee61c6531 100644 --- a/gstd/src/config.rs +++ b/gstd/src/config.rs @@ -21,7 +21,7 @@ //! This module is for configuring `gstd` inside gear programs. use crate::{ - errors::{Error, Result}, + errors::{Error, Result, UsageError}, BlockCount, }; @@ -100,7 +100,7 @@ impl Config { /// Set `wait_for` duration (in blocks). pub fn set_wait_for(duration: BlockCount) -> Result<()> { if duration == 0 { - return Err(Error::EmptyWaitDuration); + return Err(Error::Gstd(UsageError::EmptyWaitDuration)); } unsafe { CONFIG.wait_for = duration }; @@ -121,7 +121,7 @@ impl Config { /// Set the `wait_up_to` duration (in blocks). pub fn set_wait_up_to(duration: BlockCount) -> Result<()> { if duration == 0 { - return Err(Error::EmptyWaitDuration); + return Err(Error::Gstd(UsageError::EmptyWaitDuration)); } unsafe { CONFIG.wait_up_to = duration }; @@ -142,7 +142,7 @@ impl Config { /// Set `mx_lock_duration_max` duration (in blocks). pub fn set_mx_lock_duration(duration: BlockCount) -> Result<()> { if duration == 0 { - return Err(Error::ZeroMxLockDuration); + return Err(Error::Gstd(UsageError::ZeroMxLockDuration)); } unsafe { CONFIG.mx_lock_duration = duration }; @@ -152,7 +152,7 @@ impl Config { /// Set `system_reserve` gas amount. pub fn set_system_reserve(amount: u64) -> Result<()> { if amount == 0 { - return Err(Error::ZeroSystemReservationAmount); + return Err(Error::Gstd(UsageError::ZeroSystemReservationAmount)); } unsafe { CONFIG.system_reserve = amount }; diff --git a/gstd/src/msg/async.rs b/gstd/src/msg/async.rs index 5d78ec43296..69cc108f20a 100644 --- a/gstd/src/msg/async.rs +++ b/gstd/src/msg/async.rs @@ -30,6 +30,7 @@ use core::{ task::{Context, Poll}, }; use futures::future::FusedFuture; +use gear_core_errors::ReplyCode; use scale_info::scale::Decode; fn poll(waiting_reply_to: MessageId, cx: &mut Context<'_>, f: F) -> Poll> @@ -51,15 +52,17 @@ where "Somebody created a future with the MessageId that never ended in static replies!" ), ReplyPoll::Pending => Poll::Pending, - ReplyPoll::Some((actual_reply, reply_code)) => { + ReplyPoll::Some((payload, reply_code)) => { // Remove lock after waking. async_runtime::locks().remove(msg_id, waiting_reply_to); - if !reply_code.is_success() { - return Poll::Ready(Err(Error::ReplyCode(reply_code))); + match reply_code { + ReplyCode::Success(_) => Poll::Ready(f(payload)), + ReplyCode::Error(reason) => { + Poll::Ready(Err(Error::ErrorReply(payload.into(), reason))) + } + ReplyCode::Unsupported => Poll::Ready(Err(Error::UnsupportedReply(payload))), } - - Poll::Ready(f(actual_reply)) } } } diff --git a/gstd/src/sync/mutex.rs b/gstd/src/sync/mutex.rs index 487ea1bb1cb..6a1786f2ff9 100644 --- a/gstd/src/sync/mutex.rs +++ b/gstd/src/sync/mutex.rs @@ -19,7 +19,7 @@ use super::access::AccessQueue; use crate::{ async_runtime, - errors::{Error, Result}, + errors::{Error, Result, UsageError}, exec, format, msg, BlockCount, BlockNumber, Config, MessageId, }; use core::{ @@ -290,7 +290,7 @@ impl<'a, T> MutexLockFuture<'a, T> { /// some message before the ownership can be seized by another rival pub fn own_up_for(self, block_count: BlockCount) -> Result { if block_count == 0 { - Err(Error::ZeroMxLockDuration) + Err(Error::Gstd(UsageError::ZeroMxLockDuration)) } else { Ok(MutexLockFuture { mutex_id: self.mutex_id,