diff --git a/Cargo.lock b/Cargo.lock index 115a1dd3d..a9980311d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3265,6 +3265,7 @@ dependencies = [ "tower", "tower-http", "tracing", + "wasmer", ] [[package]] diff --git a/packages/fuel-indexer-api-server/Cargo.toml b/packages/fuel-indexer-api-server/Cargo.toml index 6bb1cf2f1..9d1657746 100644 --- a/packages/fuel-indexer-api-server/Cargo.toml +++ b/packages/fuel-indexer-api-server/Cargo.toml @@ -42,6 +42,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } tower = { version = "0.4", features = ["limit", "buffer"] } tower-http = { version = "0.3", features = ["fs", "trace", "cors", "limit"] } tracing = { workspace = true } +wasmer = "4" [features] default = ["metrics"] diff --git a/packages/fuel-indexer-api-server/src/api.rs b/packages/fuel-indexer-api-server/src/api.rs index bd772bb93..81ad78bc6 100644 --- a/packages/fuel-indexer-api-server/src/api.rs +++ b/packages/fuel-indexer-api-server/src/api.rs @@ -103,6 +103,11 @@ pub enum ApiError { SqlValidator(#[from] crate::sql::SqlValidatorError), #[error("ParseError: {0:?}")] ParseError(#[from] strum::ParseError), + #[error("The forc-index version {toolchain_version} does not match the fuel-indexer version {fuel_indexer_version}.")] + ToolchainVersionMismatch { + toolchain_version: String, + fuel_indexer_version: String, + }, #[error("Other error: {0}")] OtherError(String), } @@ -168,6 +173,9 @@ impl IntoResponse for ApiError { // This is currently the only type of ParseError on the web server (StatusCode::BAD_REQUEST, format!("Invalid asset type: {e}")) } + ApiError::ToolchainVersionMismatch{fuel_indexer_version, toolchain_version} => { + (StatusCode::METHOD_NOT_ALLOWED, format!("The toolchain version {toolchain_version} that the WASM module was compiled with does not match the fuel-indexer-versiion {fuel_indexer_version}")) + } _ => (StatusCode::INTERNAL_SERVER_ERROR, generic_details), }; diff --git a/packages/fuel-indexer-api-server/src/ffi.rs b/packages/fuel-indexer-api-server/src/ffi.rs new file mode 100644 index 000000000..0f77f6a64 --- /dev/null +++ b/packages/fuel-indexer-api-server/src/ffi.rs @@ -0,0 +1,48 @@ +use wasmer::{AsStoreMut, Instance, MemoryView, StoreMut, WasmPtr}; + +pub(crate) fn check_wasm_toolchain_version(data: Vec) -> anyhow::Result { + let mut store = wasmer::Store::default(); + + let module = wasmer::Module::new(&store, data.clone())?; + + let imports = wasmer::imports! {}; + + let instance = wasmer::Instance::new(&mut store, &module, &imports)?; + + let version = get_toolchain_version(&mut store.as_store_mut(), &instance)?; + + Ok(version) +} + +/// Get the toolchain version stored in the WASM module. +pub fn get_toolchain_version( + store: &mut StoreMut, + instance: &Instance, +) -> anyhow::Result { + let exports = &instance.exports; + + let ptr = exports + .get_function("get_toolchain_version_ptr")? + .call(store, &[])?[0] + .i32() + .ok_or_else(|| anyhow::anyhow!("get_toolchain_version_ptr".to_string()))? + as u32; + + let len = exports + .get_function("get_toolchain_version_len")? + .call(store, &[])?[0] + .i32() + .ok_or_else(|| anyhow::anyhow!("get_toolchain_version_len".to_string()))? + as u32; + + let memory = exports.get_memory("memory")?.view(store); + let version = get_string(&memory, ptr, len)?; + + Ok(version) +} + +/// Fetch the string at the given pointer from memory. +fn get_string(mem: &MemoryView, ptr: u32, len: u32) -> anyhow::Result { + let result = WasmPtr::::new(ptr).read_utf8_string(mem, len)?; + Ok(result) +} diff --git a/packages/fuel-indexer-api-server/src/lib.rs b/packages/fuel-indexer-api-server/src/lib.rs index 9d510690c..e3ade4b2a 100644 --- a/packages/fuel-indexer-api-server/src/lib.rs +++ b/packages/fuel-indexer-api-server/src/lib.rs @@ -3,6 +3,7 @@ pub mod api; pub mod cli; pub(crate) mod commands; +pub(crate) mod ffi; pub(crate) mod middleware; pub(crate) mod models; pub(crate) mod sql; diff --git a/packages/fuel-indexer-api-server/src/uses.rs b/packages/fuel-indexer-api-server/src/uses.rs index e0f28ecf1..323f36793 100644 --- a/packages/fuel-indexer-api-server/src/uses.rs +++ b/packages/fuel-indexer-api-server/src/uses.rs @@ -221,9 +221,18 @@ pub(crate) async fn register_indexer_assets( let multipart = multipart.ok_or_else(ApiError::default)?; - let (replace_indexer, asset_bytes) = + let (toolchain_version, replace_indexer, asset_bytes) = parse_register_indexer_multipart(multipart).await?; + let fuel_indexer_version = env!("CARGO_PKG_VERSION").to_string(); + + if toolchain_version != fuel_indexer_version { + return Err(ApiError::ToolchainVersionMismatch { + toolchain_version, + fuel_indexer_version, + }); + } + queries::start_transaction(&mut conn).await?; let result = register_indexer_assets_transaction( @@ -374,7 +383,8 @@ async fn register_indexer_assets_transaction( // schema, and the WASM module. async fn parse_register_indexer_multipart( mut multipart: Multipart, -) -> ApiResult<(bool, Vec<(IndexerAssetType, Vec)>)> { +) -> ApiResult<(String, bool, Vec<(IndexerAssetType, Vec)>)> { + let mut toolchain_version: String = "unknown".to_string(); let mut replace_indexer: bool = false; let mut assets: Vec<(IndexerAssetType, Vec)> = vec![]; @@ -390,12 +400,17 @@ async fn parse_register_indexer_multipart( } name => { let asset_type = IndexerAssetType::from_str(name)?; + if asset_type == IndexerAssetType::Wasm { + toolchain_version = + crate::ffi::check_wasm_toolchain_version(data.clone().into()) + .unwrap_or("none".to_string()); + }; assets.push((asset_type, data.to_vec())); } }; } - Ok((replace_indexer, assets)) + Ok((toolchain_version, replace_indexer, assets)) } /// Return a `Nonce` to be used for authentication. diff --git a/packages/fuel-indexer-macros/src/schema.rs b/packages/fuel-indexer-macros/src/schema.rs index 366438e83..6192b3706 100644 --- a/packages/fuel-indexer-macros/src/schema.rs +++ b/packages/fuel-indexer-macros/src/schema.rs @@ -59,10 +59,14 @@ pub(crate) fn process_graphql_schema( let version_tokens = const_item("VERSION", schema.version()); + let toolchain_version_tokens = + const_item("TOOLCHAIN_VERSION", env!("CARGO_PKG_VERSION")); + let mut output = quote! { #namespace_tokens #identifer_tokens #version_tokens + #toolchain_version_tokens }; let schema = diff --git a/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.wasm b/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.wasm index e69de29bb..308990475 100644 Binary files a/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.wasm and b/packages/fuel-indexer-tests/indexers/simple-wasm/simple_wasm.wasm differ diff --git a/packages/fuel-indexer/src/ffi.rs b/packages/fuel-indexer/src/ffi.rs index d0955a555..f0a9fb4cb 100644 --- a/packages/fuel-indexer/src/ffi.rs +++ b/packages/fuel-indexer/src/ffi.rs @@ -35,7 +35,7 @@ pub enum FFIError { None(String), } -/// Get the version of the indexer schema stored in the WASM instance. +/// Get the version of the indexer schema stored in the WASM module. pub fn get_version(store: &mut StoreMut, instance: &Instance) -> FFIResult { let exports = &instance.exports;