Skip to content

Commit

Permalink
feat: rework contract runtime error processing (#4181)
Browse files Browse the repository at this point in the history
* feat: rework Wasmer1 error processing

Make sure errors processed in the similar manner between Wasmer 0.x and Wasmer 1.x.
Introduced explicit non-deterministic error.

Test plan
---------
Reworked error cases tests to make them uniform, then `cargo test`.
  • Loading branch information
olonho authored Apr 14, 2021
1 parent 0254511 commit a6bf293
Show file tree
Hide file tree
Showing 13 changed files with 436 additions and 131 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 44 additions & 4 deletions core/primitives/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::fmt::{Debug, Display};

use crate::hash::CryptoHash;
use near_rpc_error_macro::RpcError;
use near_vm_errors::{FunctionCallError, VMLogicError};
use near_vm_errors::{CompilationError, FunctionCallErrorSer, MethodResolveError, VMLogicError};

/// Error returned in the ExecutionOutcome in case of failure
#[derive(
Expand Down Expand Up @@ -332,6 +332,46 @@ pub struct ActionError {
pub kind: ActionErrorKind,
}

#[derive(Debug, Clone, PartialEq, Eq, RpcError)]
pub enum ContractCallError {
MethodResolveError(MethodResolveError),
CompilationError(CompilationError),
ExecutionError { msg: String },
}

impl From<ContractCallError> for FunctionCallErrorSer {
fn from(e: ContractCallError) -> Self {
match e {
ContractCallError::CompilationError(e) => FunctionCallErrorSer::CompilationError(e),
ContractCallError::MethodResolveError(e) => FunctionCallErrorSer::MethodResolveError(e),
ContractCallError::ExecutionError { msg } => FunctionCallErrorSer::ExecutionError(msg),
}
}
}

impl From<FunctionCallErrorSer> for ContractCallError {
fn from(e: FunctionCallErrorSer) -> Self {
match e {
FunctionCallErrorSer::CompilationError(e) => ContractCallError::CompilationError(e),
FunctionCallErrorSer::MethodResolveError(e) => ContractCallError::MethodResolveError(e),
FunctionCallErrorSer::ExecutionError(msg) => ContractCallError::ExecutionError { msg },
FunctionCallErrorSer::LinkError { msg } => ContractCallError::ExecutionError { msg },
FunctionCallErrorSer::WasmUnknownError => {
ContractCallError::ExecutionError { msg: "unknown error".to_string() }
}
FunctionCallErrorSer::EvmError(e) => {
ContractCallError::ExecutionError { msg: format!("EVM: {:?}", e) }
}
FunctionCallErrorSer::WasmTrap(e) => {
ContractCallError::ExecutionError { msg: format!("WASM: {:?}", e) }
}
FunctionCallErrorSer::HostError(e) => {
ContractCallError::ExecutionError { msg: format!("Host: {:?}", e) }
}
}
}
}

#[derive(
BorshSerialize, BorshDeserialize, Debug, Clone, PartialEq, Eq, Deserialize, Serialize, RpcError,
)]
Expand Down Expand Up @@ -384,8 +424,8 @@ pub enum ActionErrorKind {
#[serde(with = "u128_dec_format")]
minimum_stake: Balance,
},
/// An error occurred during a `FunctionCall` Action.
FunctionCallError(FunctionCallError),
/// An error occurred during a `FunctionCall` Action, parameter is debug message.
FunctionCallError(FunctionCallErrorSer),
/// Error occurs when a new `ActionReceipt` created by the `FunctionCall` action fails
/// receipt validation.
NewReceiptValidationError(ReceiptValidationError),
Expand Down Expand Up @@ -689,7 +729,7 @@ impl Display for ActionErrorKind {
ActionErrorKind::DeleteAccountStaking { account_id } => {
write!(f, "Account {:?} is staking and can not be deleted", account_id)
}
ActionErrorKind::FunctionCallError(s) => write!(f, "{}", s),
ActionErrorKind::FunctionCallError(s) => write!(f, "{:?}", s),
ActionErrorKind::NewReceiptValidationError(e) => {
write!(f, "An new action receipt created during a FunctionCall is not valid: {}", e)
}
Expand Down
59 changes: 40 additions & 19 deletions runtime/near-vm-errors/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::fmt::{self, Error, Formatter};

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};

use near_rpc_error_macro::RpcError;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)]
// TODO: remove serialization derives, once fix compilation caching.
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub enum VMError {
FunctionCallError(FunctionCallError),
/// Serialized external error from External trait implementation.
Expand All @@ -17,10 +17,34 @@ pub enum VMError {
CacheError(CacheError),
}

#[derive(
Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
)]
// TODO(4217): remove serialization derives, once fix compilation caching.
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub enum FunctionCallError {
/// Wasm compilation error
CompilationError(CompilationError),
/// Wasm binary env link error
LinkError {
msg: String,
},
/// Import/export resolve error
MethodResolveError(MethodResolveError),
/// A trap happened during execution of a binary
WasmTrap(WasmTrap),
WasmUnknownError {
debug_message: String,
},
HostError(HostError),
EvmError(EvmError),
/// Non-deterministic error.
Nondeterministic(String),
}

/// Serializable version of `FunctionCallError`. Must never reorder/remove elements, can only
/// add new variants at the end (but do that very carefully). This type must be never used
/// directly, and must be converted to `ContractCallError` instead using `into()` converter.
/// It describes stable serialization format, and only used by serialization logic.
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
pub enum FunctionCallErrorSer {
/// Wasm compilation error
CompilationError(CompilationError),
/// Wasm binary env link error
Expand All @@ -34,13 +58,9 @@ pub enum FunctionCallError {
WasmUnknownError,
HostError(HostError),
EvmError(EvmError),
/// An error message when wasmer 1.0 returns a wasmer::RuntimeError
WasmerRuntimeError(String),
/// A trap in Wasmer 1.0, not same as WasmTrap above, String is a machine readable form like "stk_ovf"
/// String is used instead of wasmer internal enum is because of BorshSerializable.
/// It can be convert back by wasmer_vm::TrapCode::from_str
Wasmer1Trap(String),
ExecutionError(String),
}

#[derive(
Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
)]
Expand All @@ -67,8 +87,8 @@ pub enum WasmTrap {
IllegalArithmetic,
/// Misaligned atomic access trap.
MisalignedAtomicAccess,
/// Breakpoint trap.
BreakpointTrap,
/// Indirect call to null.
IndirectCallToNull,
/// Stack overflow.
StackOverflow,
/// Generic trap.
Expand Down Expand Up @@ -363,12 +383,13 @@ impl fmt::Display for FunctionCallError {
FunctionCallError::HostError(e) => e.fmt(f),
FunctionCallError::LinkError { msg } => write!(f, "{}", msg),
FunctionCallError::WasmTrap(trap) => write!(f, "WebAssembly trap: {}", trap),
FunctionCallError::WasmUnknownError => {
write!(f, "Unknown error during Wasm contract execution")
FunctionCallError::WasmUnknownError { debug_message } => {
write!(f, "Unknown error during Wasm contract execution: {}", debug_message)
}
FunctionCallError::EvmError(e) => write!(f, "EVM: {:?}", e),
FunctionCallError::WasmerRuntimeError(e) => write!(f, "Wasmer Runtime: {}", e),
FunctionCallError::Wasmer1Trap(e) => write!(f, "Wasmer 1.0 trap: {}", e),
FunctionCallError::Nondeterministic(msg) => {
write!(f, "Nondeterministic error during contract execution: {}", msg)
}
}
}
}
Expand All @@ -387,8 +408,8 @@ impl fmt::Display for WasmTrap {
}
WasmTrap::MisalignedAtomicAccess => write!(f, "Misaligned atomic access trap."),
WasmTrap::GenericTrap => write!(f, "Generic trap."),
WasmTrap::BreakpointTrap => write!(f, "Breakpoint trap."),
WasmTrap::StackOverflow => write!(f, "Stack overflow."),
WasmTrap::IndirectCallToNull => write!(f, "Indirect call to null."),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions runtime/near-vm-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ wasmer-types = { version = "1.0.2", optional = true }
wasmer-compiler-singlepass = { version = "1.0.2", optional = true }
wasmer-compiler-cranelift = { version = "1.0.2", optional = true }
wasmer-engine-native = { version = "1.0.2", optional = true }
wasmer-vm = { version = "1.0.2" }
pwasm-utils = "0.12"
parity-wasm = "0.41"
wasmtime = { version = "0.25.0", default-features = false, optional = true }
Expand Down
Loading

0 comments on commit a6bf293

Please sign in to comment.