Skip to content

Commit

Permalink
feat(gstd): Add ability to get payload of error replies in async mess…
Browse files Browse the repository at this point in the history
…aging (#3833)
  • Loading branch information
breathx authored Apr 15, 2024
1 parent fd72ffd commit 0ed4c5a
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 40 deletions.
6 changes: 3 additions & 3 deletions gstd/src/async_runtime/locks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use crate::{
collections::BTreeMap,
config::WaitType,
errors::{Error, Result},
errors::{Error, Result, UsageError},
exec,
sync::MutexId,
BlockCount, BlockNumber, Config, MessageId,
Expand Down Expand Up @@ -47,7 +47,7 @@ impl Lock {
/// Wait for
pub fn exactly(b: BlockCount) -> Result<Self> {
if b == 0 {
return Err(Error::EmptyWaitDuration);
return Err(Error::Gstd(UsageError::EmptyWaitDuration));
}

Ok(Self {
Expand All @@ -59,7 +59,7 @@ impl Lock {
/// Wait up to
pub fn up_to(b: BlockCount) -> Result<Self> {
if b == 0 {
return Err(Error::EmptyWaitDuration);
return Err(Error::Gstd(UsageError::EmptyWaitDuration));
}

Ok(Self {
Expand Down
143 changes: 118 additions & 25 deletions gstd/src/common/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -33,50 +34,77 @@ pub type Result<T, E = Error> = core::result::Result<T, E>;
/// 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<u8>),

/// Timeout reached while expecting for reply.
///
/// NOTE: this error could only be returned from async messaging.
Timeout(u32, u32),
}

impl Error {
/// Check whether an error is [`Error::Timeout`].
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."),
}
}
}
Expand All @@ -87,6 +115,71 @@ impl From<CoreError> for Error {
}
}

/// New-type representing error reply payload. Expected to be utf-8 string.
#[derive(Clone, Eq, PartialEq)]
pub struct ErrorReplyPayload(pub Vec<u8>);

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<Vec<u8>> for ErrorReplyPayload {
fn from(value: Vec<u8>) -> 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<T> {
fn into_result(self) -> Result<T>;
}
Expand Down
10 changes: 5 additions & 5 deletions gstd/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

//! This module is for configuring `gstd` inside gear programs.
use crate::{
errors::{Error, Result},
errors::{Error, Result, UsageError},
BlockCount,
};

Expand Down Expand Up @@ -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 };
Expand All @@ -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 };
Expand All @@ -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 };
Expand All @@ -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 };
Expand Down
13 changes: 8 additions & 5 deletions gstd/src/msg/async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<F, R>(waiting_reply_to: MessageId, cx: &mut Context<'_>, f: F) -> Poll<Result<R>>
Expand All @@ -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))
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions gstd/src/sync/mutex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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<Self> {
if block_count == 0 {
Err(Error::ZeroMxLockDuration)
Err(Error::Gstd(UsageError::ZeroMxLockDuration))
} else {
Ok(MutexLockFuture {
mutex_id: self.mutex_id,
Expand Down

0 comments on commit 0ed4c5a

Please sign in to comment.