diff --git a/Cargo.lock b/Cargo.lock index 9722fc1e6..257dba9ba 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..05f7ecf6b 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!("WASM module toolchain version `{toolchain_version}` does not match fuel-indexer version `{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..81cfff951 --- /dev/null +++ b/packages/fuel-indexer-api-server/src/ffi.rs @@ -0,0 +1,75 @@ +use wasmer::{ + imports, AsStoreMut, Exports, Function, Instance, MemoryView, StoreMut, WasmPtr, +}; + +/// Extract the `TOOLCHAIN_VERSION` string from a WASM module. This function +/// creates a `wasmer::Instance` in order to do this. +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 mut exports = Exports::new(); + exports.insert( + "ff_put_object".to_string(), + Function::new_typed(&mut store, |_: i64, _: i32, _: i32| {}), + ); + exports.insert( + "ff_get_object".to_string(), + Function::new_typed(&mut store, |_: i64, _: i32, _: i32| 0i32), + ); + exports.insert( + "ff_early_exit".to_string(), + Function::new_typed(&mut store, |_: i32| {}), + ); + exports.insert( + "ff_put_many_to_many_record".to_string(), + Function::new_typed(&mut store, |_: i32, _: i32| {}), + ); + exports.insert( + "ff_log_data".to_string(), + Function::new_typed(&mut store, |_: i32, _: i32, _: i32| {}), + ); + + let mut imports = imports! {}; + wasmer::Imports::register_namespace(&mut imports, "env", exports); + + 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..7b02c8a99 100644 --- a/packages/fuel-indexer-api-server/src/uses.rs +++ b/packages/fuel-indexer-api-server/src/uses.rs @@ -221,9 +221,20 @@ 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 !config.disable_toolchain_version_check + && 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 +385,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 +402,23 @@ 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()) + .map_err(|e| { + tracing::warn!( + "Failed to get WASM module toolchain version: {e}" + ); + e + }) + .unwrap_or(toolchain_version); + }; 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-lib/src/config/cli.rs b/packages/fuel-indexer-lib/src/config/cli.rs index 731e6488f..319aaa978 100644 --- a/packages/fuel-indexer-lib/src/config/cli.rs +++ b/packages/fuel-indexer-lib/src/config/cli.rs @@ -198,6 +198,13 @@ pub struct IndexerArgs { help = "Allow missing blocks or non-sequential block processing." )] pub allow_non_sequential_blocks: bool, + + /// By default, Fuel Indexer will only accept WASM indexer modules compiled with the same toolchain version as the version of Fuel Indexer. + #[clap( + long, + help = "By default, Fuel Indexer will only accept WASM indexer modules compiled with the same toolchain version as the version of Fuel Indexer." + )] + pub disable_toolchain_version_check: bool, } #[derive(Debug, Parser, Clone)] @@ -323,4 +330,10 @@ pub struct ApiServerArgs { /// Allow the web server to accept raw SQL queries. #[clap(long, help = "Allow the web server to accept raw SQL queries.")] pub accept_sql_queries: bool, + /// By default, Fuel Indexer will only accept WASM indexer modules compiled with the same toolchain version as the version of Fuel Indexer. + #[clap( + long, + help = "By default, Fuel Indexer will only accept WASM indexer modules compiled with the same toolchain version as the version of Fuel Indexer." + )] + pub disable_toolchain_version_check: bool, } diff --git a/packages/fuel-indexer-lib/src/config/mod.rs b/packages/fuel-indexer-lib/src/config/mod.rs index fb3cf3874..7efa28699 100644 --- a/packages/fuel-indexer-lib/src/config/mod.rs +++ b/packages/fuel-indexer-lib/src/config/mod.rs @@ -108,6 +108,7 @@ impl Default for IndexerArgs { accept_sql_queries: defaults::ACCEPT_SQL, block_page_size: defaults::NODE_BLOCK_PAGE_SIZE, allow_non_sequential_blocks: defaults::ALLOW_NON_SEQUENTIAL_BLOCKS, + disable_toolchain_version_check: defaults::DISABLE_TOOLCHAIN_VERSION_CHECK, } } } @@ -138,6 +139,7 @@ pub struct IndexerConfig { pub accept_sql_queries: bool, pub block_page_size: usize, pub allow_non_sequential_blocks: bool, + pub disable_toolchain_version_check: bool, } impl Default for IndexerConfig { @@ -160,6 +162,7 @@ impl Default for IndexerConfig { accept_sql_queries: defaults::ACCEPT_SQL, block_page_size: defaults::NODE_BLOCK_PAGE_SIZE, allow_non_sequential_blocks: defaults::ALLOW_NON_SEQUENTIAL_BLOCKS, + disable_toolchain_version_check: defaults::DISABLE_TOOLCHAIN_VERSION_CHECK, } } } @@ -242,6 +245,7 @@ impl From for IndexerConfig { accept_sql_queries: args.accept_sql_queries, block_page_size: args.block_page_size, allow_non_sequential_blocks: args.allow_non_sequential_blocks, + disable_toolchain_version_check: args.disable_toolchain_version_check, }; config @@ -330,6 +334,7 @@ impl From for IndexerConfig { accept_sql_queries: args.accept_sql_queries, block_page_size: defaults::NODE_BLOCK_PAGE_SIZE, allow_non_sequential_blocks: defaults::ALLOW_NON_SEQUENTIAL_BLOCKS, + disable_toolchain_version_check: args.disable_toolchain_version_check, }; config diff --git a/packages/fuel-indexer-lib/src/defaults.rs b/packages/fuel-indexer-lib/src/defaults.rs index fa14ebef0..ad189a2de 100644 --- a/packages/fuel-indexer-lib/src/defaults.rs +++ b/packages/fuel-indexer-lib/src/defaults.rs @@ -137,3 +137,6 @@ pub const ACCEPT_SQL: bool = false; /// Allow missing blocks or non-sequential block processing. pub const ALLOW_NON_SEQUENTIAL_BLOCKS: bool = false; + +/// By default, Fuel Indexer will only accept WASM indexer modules compiled with the same toolchain version as the version of Fuel Indexer. +pub const DISABLE_TOOLCHAIN_VERSION_CHECK: bool = false; 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..2437b471e 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-tests/tests/snapshots/integration_tests__commands__default_indexer_config.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__default_indexer_config.snap index 83519cde3..59a847f27 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__default_indexer_config.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__default_indexer_config.snap @@ -40,4 +40,5 @@ replace_indexer: false accept_sql_queries: false block_page_size: 20 allow_non_sequential_blocks: false +disable_toolchain_version_check: false diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_start_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_start_help_output.snap index da18a60e1..723751ee6 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_start_help_output.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__forc_index_start_help_output.snap @@ -29,6 +29,10 @@ OPTIONS: --database Database type. [default: postgres] [possible values: postgres] + --disable-toolchain-version-check + By default, Fuel Indexer will only accept WASM indexer modules compiled with the same + toolchain version as the version of Fuel Indexer. + --embedded-database Automatically create and start database using provided options or defaults. diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_api_server_run_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_api_server_run_help_output.snap index 1564a42e7..9b32e3ef2 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_api_server_run_help_output.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_api_server_run_help_output.snap @@ -23,6 +23,10 @@ OPTIONS: --database Database type. [default: postgres] [possible values: postgres] + --disable-toolchain-version-check + By default, Fuel Indexer will only accept WASM indexer modules compiled with the same + toolchain version as the version of Fuel Indexer. + --fuel-node-host Host of the running Fuel node. [default: localhost] diff --git a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_run_help_output.snap b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_run_help_output.snap index 80255a0ec..f7f9ca6c8 100644 --- a/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_run_help_output.snap +++ b/packages/fuel-indexer-tests/tests/snapshots/integration_tests__commands__fuel_indexer_run_help_output.snap @@ -29,6 +29,10 @@ OPTIONS: --database Database type. [default: postgres] [possible values: postgres] + --disable-toolchain-version-check + By default, Fuel Indexer will only accept WASM indexer modules compiled with the same + toolchain version as the version of Fuel Indexer. + --embedded-database Automatically create and start database using provided options or defaults. diff --git a/plugins/forc-index/src/ops/forc_index_start.rs b/plugins/forc-index/src/ops/forc_index_start.rs index a94371b92..92c458a22 100644 --- a/plugins/forc-index/src/ops/forc_index_start.rs +++ b/plugins/forc-index/src/ops/forc_index_start.rs @@ -40,6 +40,7 @@ pub async fn init(command: StartCommand) -> anyhow::Result<()> { accept_sql_queries, block_page_size, allow_non_sequential_blocks, + disable_toolchain_version_check, } = command; let mut cmd = Command::new("fuel-indexer"); @@ -93,6 +94,10 @@ pub async fn init(command: StartCommand) -> anyhow::Result<()> { ("--verbose", verbose), ("--local-fuel-node", local_fuel_node), ("--allow-non-sequential-blocks", allow_non_sequential_blocks), + ( + "--disable-toolchain-version-check", + disable_toolchain_version_check, + ), ]; for (opt, value) in options.iter() { if *value {