Skip to content

Commit

Permalink
Implement error codes for FFI WASM functions
Browse files Browse the repository at this point in the history
  • Loading branch information
deekerno committed Aug 25, 2023
1 parent 35235c3 commit 62a73bb
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 28 deletions.
54 changes: 54 additions & 0 deletions packages/fuel-indexer-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,60 @@ impl ExecutionSource {
}
}

#[derive(Debug, Clone, Copy)]
pub enum WasmIndexerError {
DeserializationError = 1,
SerializationError,
PutObjectError,
UnableToSaveListType,
UninitializedMemory,
UnableToFetchLogString,
GeneralError,
}

impl From<i32> for WasmIndexerError {
fn from(value: i32) -> Self {
match value {
0 => unreachable!("WasmIndexerError index starts at 1"),
1 => Self::DeserializationError,
2 => Self::SerializationError,
3 => Self::PutObjectError,
4 => Self::UnableToSaveListType,
5 => Self::UninitializedMemory,
6 => Self::UnableToFetchLogString,
_ => Self::GeneralError,
}
}
}

impl std::fmt::Display for WasmIndexerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SerializationError => {
write!(f, "Failed to serialize")
}
Self::DeserializationError => {
write!(f, "Failed to deserialize")
}
Self::UnableToSaveListType => {
write!(f, "Failed to save list")
}
Self::PutObjectError => {
write!(f, "Failed to save object")
}
Self::UninitializedMemory => {
write!(f, "Failed to create MemoryView for indexer")
}
Self::UnableToFetchLogString => {
write!(f, "Failed to fetch log string")
}
Self::GeneralError => write!(f, "A WASM error occurred"),
}
}
}

impl std::error::Error for WasmIndexerError {}

/// Return a fully qualified indexer namespace.
pub fn fully_qualified_namespace(namespace: &str, identifier: &str) -> String {
format!("{}_{}", namespace, identifier)
Expand Down
72 changes: 61 additions & 11 deletions packages/fuel-indexer-plugin/src/wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use alloc::vec::Vec;
use fuel_indexer_lib::{
graphql::MAX_FOREIGN_KEY_LIST_FIELDS,
utils::{deserialize, serialize},
WasmIndexerError,
};
use fuel_indexer_schema::{
join::{JoinMetadata, RawQuery},
Expand All @@ -17,11 +18,14 @@ pub use sha2::{Digest, Sha256};
pub use std::collections::{HashMap, HashSet};

extern "C" {
// TODO: error codes? or just panic and let the runtime handle it?
// TODO: How do we want to return an error code for
// a function that returns a u32 but actually uses a u8?
fn ff_get_object(type_id: i64, ptr: *const u8, len: *mut u8) -> *mut u8;
fn ff_put_object(type_id: i64, ptr: *const u8, len: u32);
fn ff_put_many_to_many_record(ptr: *const u8, len: u32);
// log_data prints information to stdout.
fn ff_log_data(ptr: *const u8, len: u32, log_level: u32);
// Put methods have error codes.
fn ff_put_object(type_id: i64, ptr: *const u8, len: u32) -> i32;
fn ff_put_many_to_many_record(ptr: *const u8, len: u32) -> i32;
}

// TODO: more to do here, hook up to 'impl log::Log for Logger'
Expand Down Expand Up @@ -49,19 +53,29 @@ impl Logger {
}
}

/// Trait for a type entity.
///
/// Any entity type that will be processed through a WASM indexer is required to implement this trait.
pub trait Entity<'a>: Sized + PartialEq + Eq + std::fmt::Debug {
/// Unique identifier for a type.
const TYPE_ID: i64;

/// Necessary metadata for saving an entity's list type fields.
const JOIN_METADATA: Option<[Option<JoinMetadata<'a>>; MAX_FOREIGN_KEY_LIST_FIELDS]>;

/// Convert database row representation into an instance of an entity.
fn from_row(vec: Vec<FtColumn>) -> Self;

/// Convert an instance of an entity into row representation for use in a database.
fn to_row(&self) -> Vec<FtColumn>;

/// Returns an entity's internal type ID.
fn type_id(&self) -> i64 {
Self::TYPE_ID
}

fn save_many_to_many(&self) {
/// Saves a record that contains a list of multiple elements.
fn save_many_to_many(&self) -> Result<(), WasmIndexerError> {
if let Some(meta) = Self::JOIN_METADATA {
let items = meta.iter().filter_map(|x| x.clone()).collect::<Vec<_>>();
let row = self.to_row();
Expand All @@ -71,40 +85,75 @@ pub trait Entity<'a>: Sized + PartialEq + Eq + std::fmt::Debug {
.filter(|query| !query.is_empty())
.collect::<Vec<_>>();
let bytes = serialize(&queries);
unsafe { ff_put_many_to_many_record(bytes.as_ptr(), bytes.len() as u32) }
unsafe {
let res = ff_put_many_to_many_record(bytes.as_ptr(), bytes.len() as u32);

if res != 0 {
return Err(WasmIndexerError::UnableToSaveListType);
}

Ok(())
}
} else {
Ok(())
}
}

/// Loads a record given a UID.
fn load(id: UID) -> Option<Self> {
Self::load_unsafe(id).unwrap()
}

/// Loads a record through the FFI with the WASM runtime and checks for errors.
fn load_unsafe(id: UID) -> Result<Option<Self>, WasmIndexerError> {
unsafe {
let buff = bincode::serialize(&id.to_string()).unwrap();
let buff = if let Ok(bytes) = bincode::serialize(&id.to_string()) {
bytes
} else {
return Err(WasmIndexerError::SerializationError);
};

let mut bufflen = (buff.len() as u32).to_le_bytes();

let ptr = ff_get_object(Self::TYPE_ID, buff.as_ptr(), bufflen.as_mut_ptr());

if !ptr.is_null() {
let len = u32::from_le_bytes(bufflen) as usize;
let bytes = Vec::from_raw_parts(ptr, len, len);
let vec = deserialize(&bytes).expect("Bad serialization.");
let vec = if let Ok(v) = deserialize(&bytes) {
v
} else {
return Err(WasmIndexerError::DeserializationError);
};

return Some(Self::from_row(vec));
return Ok(Some(Self::from_row(vec)));
}

None
Ok(None)
}
}

/// Saves a record.
fn save(&self) {
self.save_unsafe().unwrap()
}

/// Saves a record through the FFI with the WASM runtime and checks for errors.
fn save_unsafe(&self) -> Result<(), WasmIndexerError> {
unsafe {
let buf = serialize(&self.to_row());
ff_put_object(Self::TYPE_ID, buf.as_ptr(), buf.len() as u32)
let res = ff_put_object(Self::TYPE_ID, buf.as_ptr(), buf.len() as u32);
if res != 0 {
return Err(WasmIndexerError::from(res));
}
}

self.save_many_to_many();
self.save_many_to_many()
}
}

#[no_mangle]
/// Allocation function to be called by an executor in a WASM runtime.
fn alloc_fn(size: u32) -> *const u8 {
let vec = Vec::with_capacity(size as usize);
let ptr = vec.as_ptr();
Expand All @@ -115,6 +164,7 @@ fn alloc_fn(size: u32) -> *const u8 {
}

#[no_mangle]
/// Deallocation function to be called by an executor in a WASM runtime.
fn dealloc_fn(ptr: *mut u8, len: usize) {
let _vec = unsafe { Vec::from_raw_parts(ptr, len, len) };
}
50 changes: 33 additions & 17 deletions packages/fuel-indexer/src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use async_std::sync::MutexGuard;
use fuel_indexer_lib::defaults;
use fuel_indexer_lib::{defaults, WasmIndexerError};
use fuel_indexer_schema::{join::RawQuery, FtColumn};
use fuel_indexer_types::ffi::{
LOG_LEVEL_DEBUG, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_TRACE, LOG_LEVEL_WARN,
Expand Down Expand Up @@ -147,13 +147,19 @@ fn get_object(
/// Put the given type at the given pointer into memory.
///
/// This function is fallible, and will panic if the type cannot be saved.
fn put_object(mut env: FunctionEnvMut<IndexEnv>, type_id: i64, ptr: u32, len: u32) {
fn put_object(
mut env: FunctionEnvMut<IndexEnv>,
type_id: i64,
ptr: u32,
len: u32,
) -> i32 {
let (idx_env, store) = env.data_and_store_mut();
let mem = idx_env
.memory
.as_mut()
.expect("Memory unitialized")
.view(&store);

let mem = if let Some(memory) = idx_env.memory.as_mut() {
memory.view(&store)
} else {
return WasmIndexerError::UninitializedMemory as i32;
};

let mut bytes = Vec::with_capacity(len as usize);
let range = ptr as usize..ptr as usize + len as usize;
Expand All @@ -166,7 +172,7 @@ fn put_object(mut env: FunctionEnvMut<IndexEnv>, type_id: i64, ptr: u32, len: u3
Ok(columns) => columns,
Err(e) => {
error!("Failed to deserialize Vec<FtColumn> for put_object: {e:?}",);
return;
return WasmIndexerError::DeserializationError as i32;
}
};

Expand All @@ -179,18 +185,21 @@ fn put_object(mut env: FunctionEnvMut<IndexEnv>, type_id: i64, ptr: u32, len: u3
.put_object(type_id, columns, bytes)
.await
});

0
}

/// Execute the arbitrary query at the given pointer.
///
/// This function is fallible, and will panic if the query cannot be executed.
fn put_many_to_many_record(mut env: FunctionEnvMut<IndexEnv>, ptr: u32, len: u32) {
fn put_many_to_many_record(mut env: FunctionEnvMut<IndexEnv>, ptr: u32, len: u32) -> i32 {
let (idx_env, store) = env.data_and_store_mut();
let mem = idx_env
.memory
.as_mut()
.expect("Memory unitialized")
.view(&store);

let mem = if let Some(memory) = idx_env.memory.as_mut() {
memory.view(&store)
} else {
return WasmIndexerError::UninitializedMemory as i32;
};

let mut bytes = Vec::with_capacity(len as usize);
let range = ptr as usize..ptr as usize + len as usize;
Expand All @@ -199,9 +208,14 @@ fn put_many_to_many_record(mut env: FunctionEnvMut<IndexEnv>, ptr: u32, len: u32
bytes.extend_from_slice(&mem.data_unchecked()[range]);
}

let queries: Vec<RawQuery> =
bincode::deserialize(&bytes).expect("Failed to deserialize queries");
let queries = queries.iter().map(|q| q.to_string()).collect::<Vec<_>>();
let queries: Vec<String> = match bincode::deserialize::<Vec<RawQuery>>(&bytes) {
Ok(queries) => queries.iter().map(|q| q.to_string()).collect(),
Err(e) => {
error!("Failed to deserialize queries: {e:?}");
return WasmIndexerError::DeserializationError as i32;
}
};

let rt = tokio::runtime::Handle::current();
rt.block_on(async {
idx_env
Expand All @@ -211,6 +225,8 @@ fn put_many_to_many_record(mut env: FunctionEnvMut<IndexEnv>, ptr: u32, len: u32
.put_many_to_many_record(queries)
.await
});

0
}

/// Get the exports for the given store and environment.
Expand Down

0 comments on commit 62a73bb

Please sign in to comment.