Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make some host errors non-recoverable in try_call. #945

Merged
merged 4 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion soroban-env-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl From<stellar_xdr::Error> for Error {
impl From<wasmi::core::TrapCode> for Error {
fn from(code: wasmi::core::TrapCode) -> Self {
let ec = match code {
wasmi::core::TrapCode::UnreachableCodeReached => ScErrorCode::InternalError,
wasmi::core::TrapCode::UnreachableCodeReached => ScErrorCode::InvalidAction,

wasmi::core::TrapCode::MemoryOutOfBounds | wasmi::core::TrapCode::TableOutOfBounds => {
ScErrorCode::IndexBounds
Expand Down
15 changes: 11 additions & 4 deletions soroban-env-host/src/host.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,7 +701,7 @@ impl Host {
.ok_or_else(|| {
self.err(
ScErrorType::Auth,
ScErrorCode::InternalError,
ScErrorCode::InvalidAction,
"previous invocation is missing - no auth data to get",
&[],
)
Expand Down Expand Up @@ -774,7 +774,7 @@ impl Host {
.ok_or_else(|| {
self.err(
ScErrorType::Auth,
ScErrorCode::InternalError,
ScErrorCode::InvalidAction,
"previous invocation is missing - no auth data to get",
&[],
)
Expand Down Expand Up @@ -2372,7 +2372,14 @@ impl VmCallerEnv for Host {
&[func.to_val(), args.to_val()],
)
})?;
Ok(e.error.to_val())
// Only allow to gracefully handle the recoverable errors.
// Non-recoverable errors should still cause guest to panic and
// abort execution.
if e.is_recoverable() {
Ok(e.error.to_val())
} else {
Err(e)
}
}
}
}
Expand Down Expand Up @@ -2921,7 +2928,7 @@ impl VmCallerEnv for Host {
Frame::HostFunction(_) => {
return Err(self.err(
ScErrorType::Context,
ScErrorCode::InvalidAction,
ScErrorCode::InternalError,
"require_auth is not suppported for host fns",
&[],
))
Expand Down
25 changes: 25 additions & 0 deletions soroban-env-host/src/host/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,31 @@ impl HostError {
}
}
}

/// Identifies whether the error can be meaningfully recovered from.
///
/// We consider errors that occur due to broken execution preconditions (
/// such as incorrect footprint) non-recoverable.
pub fn is_recoverable(&self) -> bool {
// All internal errors that originate from the host can be considered
// non-recoverable (they should only appear if there is some bug in the
// host implementation or setup).
if !self.error.is_type(ScErrorType::Contract)
&& self.error.is_code(ScErrorCode::InternalError)
{
return false;
}
// Exceeding the budget or storage limit is non-recoverable. Exceeding
// storage 'limit' is basically accessing entries outside of the
// supplied footprint.
if self.error.is_code(ScErrorCode::ExceededLimit)
&& (self.error.is_type(ScErrorType::Storage) || self.error.is_type(ScErrorType::Budget))
{
return false;
}

true
}
}

impl<T> From<T> for HostError
Expand Down
14 changes: 10 additions & 4 deletions soroban-env-host/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,23 @@ impl Footprint {
ty: AccessType,
budget: &Budget,
) -> Result<(), HostError> {
// `ExceededLimit` is not the most precise term here, but footprint has
// to be externally supplied in a similar fashion to budget and it's
// also representing an execution resource limit (number of ledger
// entries to access), so it might be considered 'exceeded'.
// This also helps distinguish access errors from the values simply
// being missing from storage (but with a valid footprint).
if let Some(existing) = self.0.get::<Rc<LedgerKey>>(key, budget)? {
match (existing, ty) {
(AccessType::ReadOnly, AccessType::ReadOnly) => Ok(()),
(AccessType::ReadOnly, AccessType::ReadWrite) => {
Err((ScErrorType::Storage, ScErrorCode::InvalidAction).into())
Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
}
(AccessType::ReadWrite, AccessType::ReadOnly) => Ok(()),
(AccessType::ReadWrite, AccessType::ReadWrite) => Ok(()),
}
} else {
Err((ScErrorType::Storage, ScErrorCode::MissingValue).into())
Err((ScErrorType::Storage, ScErrorCode::ExceededLimit).into())
}
}
}
Expand Down Expand Up @@ -420,7 +426,7 @@ mod test_footprint {
let res = fp.enforce_access(&key, AccessType::ReadOnly, &budget);
assert!(HostError::result_matches_err(
res,
(ScErrorType::Storage, ScErrorCode::MissingValue)
(ScErrorType::Storage, ScErrorCode::ExceededLimit)
));
Ok(())
}
Expand All @@ -440,7 +446,7 @@ mod test_footprint {
let res = fp.enforce_access(&key, AccessType::ReadWrite, &budget);
assert!(HostError::result_matches_err(
res,
(ScErrorType::Storage, ScErrorCode::InvalidAction)
(ScErrorType::Storage, ScErrorCode::ExceededLimit)
));
Ok(())
}
Expand Down
6 changes: 3 additions & 3 deletions soroban-env-host/src/test/hostile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn hostile_ssmash_traps() -> Result<(), HostError> {

assert!(HostError::result_matches_err(
res,
(ScErrorType::WasmVm, ScErrorCode::InternalError)
(ScErrorType::WasmVm, ScErrorCode::InvalidAction)
));
Ok(())
}
Expand All @@ -73,7 +73,7 @@ fn hostile_oob1_traps() -> Result<(), HostError> {

assert!(HostError::result_matches_err(
res,
(ScErrorType::WasmVm, ScErrorCode::InternalError)
(ScErrorType::WasmVm, ScErrorCode::InvalidAction)
));
Ok(())
}
Expand All @@ -90,7 +90,7 @@ fn hostile_oob2_traps() -> Result<(), HostError> {
);
assert!(HostError::result_matches_err(
res,
(ScErrorType::WasmVm, ScErrorCode::InternalError)
(ScErrorType::WasmVm, ScErrorCode::InvalidAction)
));
Ok(())
}
Expand Down
8 changes: 4 additions & 4 deletions soroban-env-host/src/test/invocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn invoke_single_contract_function() -> Result<(), HostError> {
Symbol::try_from_small_str("add")?,
host.test_vec_obj(&[a, c])?,
);
let code = (ScErrorType::WasmVm, ScErrorCode::InternalError);
let code = (ScErrorType::WasmVm, ScErrorCode::InvalidAction);
assert!(HostError::result_matches_err(res, code));
Ok(())
}
Expand Down Expand Up @@ -126,7 +126,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {

// try call -- add will trap, and add_with will trap, but we will get a status
let status = host.try_call(id0_obj, sym, args)?;
let code = (ScErrorType::WasmVm, ScErrorCode::InternalError);
let code = (ScErrorType::WasmVm, ScErrorCode::InvalidAction);
let exp: Error = code.into();
assert_eq!(status.get_payload(), exp.to_val().get_payload());

Expand All @@ -135,7 +135,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
let last_event = events.last().unwrap();
// run `UPDATE_EXPECT=true cargo test` to update this.
let expected = expect![[
r#"[Diagnostic Event] topics:[error, Error(WasmVm, InternalError)], data:["contract try_call failed", add_with, [2147483647, 1, Bytes()]]"#
r#"[Diagnostic Event] topics:[error, Error(WasmVm, InvalidAction)], data:["contract try_call failed", add_with, [2147483647, 1, Bytes()]]"#
]];
let actual = format!("{}", last_event);
expected.assert_eq(&actual);
Expand All @@ -149,7 +149,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> {
let last_event = events.last().unwrap();
// run `UPDATE_EXPECT=true cargo test` to update this.
let expected = expect![[
r#"[Diagnostic Event] topics:[error, Error(WasmVm, InternalError)], data:["contract call failed", add_with, [2147483647, 1, Bytes()]]"#
r#"[Diagnostic Event] topics:[error, Error(WasmVm, InvalidAction)], data:["contract call failed", add_with, [2147483647, 1, Bytes()]]"#
]];
let actual = format!("{}", last_event);
expected.assert_eq(&actual);
Expand Down