fix(indexer): stop FFI integration tests segfaulting on query_account

The indexer FFI test helper borrowed `ctx.runtime()` via `Runtime::from_borrowed` and then moved `ctx` (and its by-value `tokio::runtime::Runtime` field) into the tuple returned from `setup()`. That move relocates the runtime, leaving the raw pointer the indexer stored dangling. Sync queries never touch the runtime, so they passed; `query_account` is the only path that `block_on`s it, so it dereferenced freed stack memory → SIGSEGV (hence only the two `indexer_ffi_state_consistency*` tests crashed).

Pass a null runtime so the FFI creates and owns its own — the same lifetime path the production module uses (`start_indexer(nullptr, …)`) — instead of borrowing a runtime whose address isn't stable across the move.
This commit is contained in:
erhant 2026-06-19 18:50:02 +03:00
parent f3134cde58
commit 0c52ec9695

View File

@ -40,10 +40,7 @@ unsafe extern "C" {
) -> InitializedIndexerServiceFFIResult;
}
pub fn setup_indexer_ffi(
runtime: &Runtime,
bedrock_addr: SocketAddr,
) -> Result<(IndexerServiceFFI, TempDir)> {
pub fn setup_indexer_ffi(bedrock_addr: SocketAddr) -> Result<(IndexerServiceFFI, TempDir)> {
let temp_indexer_dir =
tempfile::tempdir().context("Failed to create temp dir for indexer home")?;
@ -63,8 +60,9 @@ pub fn setup_indexer_ffi(
file.flush()?;
let res =
// SAFETY: lib function ensures validity of value.
unsafe { start_indexer(std::ptr::from_ref(runtime), CString::new(config_path.to_str().unwrap())?.as_ptr()) };
// SAFETY: null runtime → the FFI creates and owns its own tokio runtime,
// so there is no external runtime whose address we must keep stable.
unsafe { start_indexer(std::ptr::null(), CString::new(config_path.to_str().unwrap())?.as_ptr()) };
if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error);
@ -79,9 +77,11 @@ pub fn setup_indexer_ffi(
pub fn setup() -> Result<(BlockingTestContext, IndexerServiceFFI, TempDir)> {
let ctx = TestContext::builder().disable_indexer().build_blocking()?;
// Safety: ctx runtime is valid for the lifetime of the returned Runtime
let runtime = unsafe { Runtime::from_borrowed(ctx.runtime()) };
let (indexer_ffi, indexer_dir) = setup_indexer_ffi(&runtime, ctx.ctx().bedrock_addr())?;
// Don't borrow `ctx.runtime()`: `ctx` (and its by-value tokio runtime) is
// moved into the returned tuple, which would leave any pointer into it
// dangling. Pass a null runtime so the FFI owns its own — the same path the
// production module uses.
let (indexer_ffi, indexer_dir) = setup_indexer_ffi(ctx.ctx().bedrock_addr())?;
Ok((ctx, indexer_ffi, indexer_dir))
}