fix: ci fix

This commit is contained in:
Pravdyvy 2026-04-13 15:53:31 +03:00
parent 5f86e597d5
commit 5fc397c2ee
7 changed files with 120 additions and 179 deletions

12
Cargo.lock generated
View File

@ -3487,18 +3487,6 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "indexer_service_ffi"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"env_logger",
"indexer_ffi",
"log",
"tokio",
]
[[package]] [[package]]
name = "indexer_service_protocol" name = "indexer_service_protocol"
version = "0.1.0" version = "0.1.0"

View File

@ -37,7 +37,7 @@ members = [
"examples/program_deployment/methods/guest", "examples/program_deployment/methods/guest",
"bedrock_client", "bedrock_client",
"testnet_initial_state", "testnet_initial_state",
"indexer_ffi", "indexer_service_ffi", "indexer_ffi",
] ]
[workspace.dependencies] [workspace.dependencies]

View File

@ -18,36 +18,51 @@ impl IndexerServiceFFI {
} }
} }
// Helper to safely take ownership back /// Helper to take ownership back.
///
/// # Safety
///
/// The caller must ensure that:
/// - `self` is a valid object(contains valid pointers in all fields)
#[must_use] #[must_use]
pub fn into_parts(self) -> (Box<IndexerHandle>, Box<Runtime>) { pub unsafe fn into_parts(self) -> (Box<IndexerHandle>, Box<Runtime>) {
let indexer_handle = unsafe { Box::from_raw(self.indexer_handle.cast::<IndexerHandle>()) }; let indexer_handle = unsafe { Box::from_raw(self.indexer_handle.cast::<IndexerHandle>()) };
let runtime = unsafe { Box::from_raw(self.runtime.cast::<Runtime>()) }; let runtime = unsafe { Box::from_raw(self.runtime.cast::<Runtime>()) };
(indexer_handle, runtime) (indexer_handle, runtime)
} }
// Helper to get indexer handle addr /// Helper to get indexer handle addr.
pub unsafe fn addr(&self) -> SocketAddr { ///
/// # Safety
///
/// The caller must ensure that:
/// - `self` is a valid object(contains valid pointers in all fields)
#[must_use]
pub const unsafe fn addr(&self) -> SocketAddr {
let indexer_handle = unsafe { let indexer_handle = unsafe {
self.indexer_handle self.indexer_handle
.cast::<IndexerHandle>() .cast::<IndexerHandle>()
.as_ref() .as_ref()
.expect("Indexr Handle must be non-null pointer") .expect("Indexer Handle must be non-null pointer")
}; };
indexer_handle.addr() indexer_handle.addr()
} }
// Helper to get indexer handle addr /// Helper to get indexer handle addr.
pub unsafe fn handle(&self) -> &IndexerHandle { ///
let indexer_handle = unsafe { /// # Safety
///
/// The caller must ensure that:
/// - `self` is a valid object(contains valid pointers in all fields)
#[must_use]
pub const unsafe fn handle(&self) -> &IndexerHandle {
unsafe {
self.indexer_handle self.indexer_handle
.cast::<IndexerHandle>() .cast::<IndexerHandle>()
.as_ref() .as_ref()
.expect("Indexr Handle must be non-null pointer") .expect("Indexer Handle must be non-null pointer")
}; }
indexer_handle
} }
} }

View File

@ -1,17 +0,0 @@
[package]
name = "indexer_service_ffi"
version = "0.1.0"
edition = "2024"
license.workspace = true
[dependencies]
indexer_ffi.workspace = true
log.workspace = true
clap.workspace = true
anyhow.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal"] }
env_logger.workspace = true
[lints]
workspace = true

View File

@ -1,37 +0,0 @@
use std::{ffi::{CString, c_char}, path::PathBuf};
use anyhow::Result;
use clap::Parser;
use indexer_ffi::api::lifecycle::InitializedIndexerServiceFFIResult;
use log::info;
#[derive(Debug, Parser)]
#[clap(version)]
struct Args {
#[clap(name = "config")]
config_path: PathBuf,
#[clap(short, long, default_value = "8779")]
port: u16,
}
unsafe extern "C" {
fn start_indexer(config_path: *const c_char, port: u16) -> InitializedIndexerServiceFFIResult;
}
fn main() -> Result<()> {
env_logger::init();
let Args { config_path, port } = Args::parse();
let res =
unsafe { start_indexer(CString::new(config_path.to_str().unwrap())?.as_ptr(), port) };
if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error);
}
loop {
std::thread::sleep(std::time::Duration::from_secs(10));
info!("Running...");
}
}

View File

@ -1,21 +1,25 @@
use std::{ use std::{
ffi::{CString, c_char}, fs::File, io::Write, net::SocketAddr, path::PathBuf, sync::Arc ffi::{CString, c_char},
fs::File,
io::Write as _,
net::SocketAddr,
path::PathBuf,
sync::Arc,
}; };
use anyhow::{Context, Result, bail}; use anyhow::{Context as _, Result, bail};
use futures::FutureExt; use futures::FutureExt as _;
use indexer_ffi::{IndexerServiceFFI, api::lifecycle::InitializedIndexerServiceFFIResult}; use indexer_ffi::{IndexerServiceFFI, api::lifecycle::InitializedIndexerServiceFFIResult};
use indexer_service_rpc::RpcClient as _;
use log::{debug, error, warn}; use log::{debug, error, warn};
use nssa::AccountId; use nssa::AccountId;
use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait}; use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _};
use sequencer_service::SequencerHandle; use sequencer_service::SequencerHandle;
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
use tempfile::TempDir; use tempfile::TempDir;
use testcontainers::compose::DockerCompose; use testcontainers::compose::DockerCompose;
use wallet::{WalletCore, config::WalletConfigOverrides}; use wallet::{WalletCore, config::WalletConfigOverrides};
use indexer_service_rpc::RpcClient as _;
use crate::{ use crate::{
BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT, LOGGER, TestContextBuilder, config, BEDROCK_SERVICE_PORT, BEDROCK_SERVICE_WITH_OPEN_PORT, LOGGER, TestContextBuilder, config,
}; };
@ -36,18 +40,24 @@ pub struct TestContextFFI {
wallet_password: String, wallet_password: String,
/// Optional to move out value in Drop. /// Optional to move out value in Drop.
sequencer_handle: Option<SequencerHandle>, sequencer_handle: Option<SequencerHandle>,
indexer_ffi: IndexerServiceFFI,
bedrock_compose: DockerCompose, bedrock_compose: DockerCompose,
_temp_indexer_dir: TempDir, _temp_indexer_dir: TempDir,
_temp_sequencer_dir: TempDir, _temp_sequencer_dir: TempDir,
_temp_wallet_dir: TempDir, _temp_wallet_dir: TempDir,
} }
#[expect(
clippy::multiple_inherent_impl,
reason = "It is more natural to have this implementation here"
)]
impl TestContextBuilder { impl TestContextBuilder {
pub fn build_ffi(self, runtime: Arc<tokio::runtime::Runtime>) -> Result<TestContextFFI> { pub fn build_ffi(
self,
runtime: &Arc<tokio::runtime::Runtime>,
) -> Result<(TestContextFFI, IndexerServiceFFI)> {
TestContextFFI::new_configured( TestContextFFI::new_configured(
self.sequencer_partial_config.unwrap_or_default(), self.sequencer_partial_config.unwrap_or_default(),
self.initial_data.unwrap_or_else(|| { &self.initial_data.unwrap_or_else(|| {
config::InitialData::with_two_public_and_two_private_initialized_accounts() config::InitialData::with_two_public_and_two_private_initialized_accounts()
}), }),
runtime, runtime,
@ -57,7 +67,7 @@ impl TestContextBuilder {
impl TestContextFFI { impl TestContextFFI {
/// Create new test context. /// Create new test context.
pub fn new(runtime: Arc<tokio::runtime::Runtime>) -> Result<Self> { pub fn new(runtime: &Arc<tokio::runtime::Runtime>) -> Result<(Self, IndexerServiceFFI)> {
Self::builder().build_ffi(runtime) Self::builder().build_ffi(runtime)
} }
@ -68,72 +78,62 @@ impl TestContextFFI {
fn new_configured( fn new_configured(
sequencer_partial_config: config::SequencerPartialConfig, sequencer_partial_config: config::SequencerPartialConfig,
initial_data: config::InitialData, initial_data: &config::InitialData,
runtime: Arc<tokio::runtime::Runtime> runtime: &Arc<tokio::runtime::Runtime>,
) -> Result<Self> { ) -> Result<(Self, IndexerServiceFFI)> {
// Ensure logger is initialized only once // Ensure logger is initialized only once
*LOGGER; *LOGGER;
debug!("Test context setup"); debug!("Test context setup");
println!("Hello 3");
runtime.block_on(async { println!("Hello 3.5") });
println!("Hello 4");
let (bedrock_compose, bedrock_addr) = runtime.block_on(Self::setup_bedrock_node())?; let (bedrock_compose, bedrock_addr) = runtime.block_on(Self::setup_bedrock_node())?;
println!("Hello 5"); let (indexer_ffi, temp_indexer_dir) = Self::setup_indexer_ffi(bedrock_addr, initial_data)
let (indexer_ffi, temp_indexer_dir) = Self::setup_indexer_ffi(bedrock_addr, &initial_data)
.context("Failed to setup Indexer")?; .context("Failed to setup Indexer")?;
println!("Hello 6"); let (sequencer_handle, temp_sequencer_dir) = runtime
.block_on(Self::setup_sequencer(
sequencer_partial_config,
bedrock_addr,
// SAFETY: addr is valid if indexer_ffi is valid.
unsafe { indexer_ffi.addr() },
initial_data,
))
.context("Failed to setup Sequencer")?;
let (sequencer_handle, temp_sequencer_dir) = runtime.block_on(Self::setup_sequencer( let (wallet, temp_wallet_dir, wallet_password) = runtime
sequencer_partial_config, .block_on(Self::setup_wallet(sequencer_handle.addr(), initial_data))
bedrock_addr, .context("Failed to setup wallet")?;
unsafe { indexer_ffi.addr() },
&initial_data,
))
.context("Failed to setup Sequencer")?;
println!("Hello 7");
let (wallet, temp_wallet_dir, wallet_password) =
runtime.block_on(Self::setup_wallet(sequencer_handle.addr(), &initial_data)
)
.context("Failed to setup wallet")?;
println!("Hello 8");
let sequencer_url = config::addr_to_url(config::UrlProtocol::Http, sequencer_handle.addr()) let sequencer_url = config::addr_to_url(config::UrlProtocol::Http, sequencer_handle.addr())
.context("Failed to convert sequencer addr to URL")?; .context("Failed to convert sequencer addr to URL")?;
let indexer_url = let indexer_url = config::addr_to_url(
config::addr_to_url(config::UrlProtocol::Ws, unsafe { indexer_ffi.addr() }) config::UrlProtocol::Ws,
.context("Failed to convert indexer addr to URL")?; // SAFETY: addr is valid if indexer_ffi is valid.
unsafe { indexer_ffi.addr() },
)
.context("Failed to convert indexer addr to URL")?;
let sequencer_client = SequencerClientBuilder::default() let sequencer_client = SequencerClientBuilder::default()
.build(sequencer_url) .build(sequencer_url)
.context("Failed to create sequencer client")?; .context("Failed to create sequencer client")?;
let indexer_client = runtime.block_on(IndexerClient::new(&indexer_url) let indexer_client = runtime
) .block_on(IndexerClient::new(&indexer_url))
.context("Failed to create indexer client")?; .context("Failed to create indexer client")?;
println!("Hello 9"); Ok((
Self {
Ok(Self { sequencer_client,
sequencer_client, indexer_client,
indexer_client, wallet,
wallet, wallet_password,
wallet_password, bedrock_compose,
bedrock_compose, sequencer_handle: Some(sequencer_handle),
sequencer_handle: Some(sequencer_handle), _temp_indexer_dir: temp_indexer_dir,
_temp_sequencer_dir: temp_sequencer_dir,
_temp_wallet_dir: temp_wallet_dir,
},
indexer_ffi, indexer_ffi,
_temp_indexer_dir: temp_indexer_dir, ))
_temp_sequencer_dir: temp_sequencer_dir,
_temp_wallet_dir: temp_wallet_dir,
})
} }
async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> { async fn setup_bedrock_node() -> Result<(DockerCompose, SocketAddr)> {
@ -233,13 +233,18 @@ impl TestContextFFI {
file.flush()?; file.flush()?;
let res = let res =
// SAFETY: lib function ensures validity of value.
unsafe { start_indexer(CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) }; unsafe { start_indexer(CString::new(config_path.to_str().unwrap())?.as_ptr(), 0) };
if res.error.is_error() { if res.error.is_error() {
anyhow::bail!("Indexer FFI error {:?}", res.error); anyhow::bail!("Indexer FFI error {:?}", res.error);
} }
Ok((unsafe { std::ptr::read(res.value) }, temp_indexer_dir)) Ok((
// SAFETY: lib function ensures validity of value.
unsafe { std::ptr::read(res.value) },
temp_indexer_dir,
))
} }
async fn setup_sequencer( async fn setup_sequencer(
@ -353,14 +358,11 @@ impl TestContextFFI {
.collect() .collect()
} }
pub fn get_last_block_sequencer(&self, runtime: Arc<tokio::runtime::Runtime>) -> Result<u64> { pub fn get_last_block_sequencer(&self, runtime: &Arc<tokio::runtime::Runtime>) -> Result<u64> {
let res = runtime.block_on(self.sequencer_client.get_last_block_id()).unwrap(); Ok(runtime.block_on(self.sequencer_client.get_last_block_id())?)
println!("Hello 11.5");
Ok(res)
} }
pub fn get_last_block_indexer(&self, runtime: Arc<tokio::runtime::Runtime>) -> Result<u64> { pub fn get_last_block_indexer(&self, runtime: &Arc<tokio::runtime::Runtime>) -> Result<u64> {
Ok(runtime.block_on(self.indexer_client.get_last_finalized_block_id())?) Ok(runtime.block_on(self.indexer_client.get_last_finalized_block_id())?)
} }
} }
@ -369,7 +371,6 @@ impl Drop for TestContextFFI {
fn drop(&mut self) { fn drop(&mut self) {
let Self { let Self {
sequencer_handle, sequencer_handle,
indexer_ffi,
bedrock_compose, bedrock_compose,
_temp_indexer_dir: _, _temp_indexer_dir: _,
_temp_sequencer_dir: _, _temp_sequencer_dir: _,
@ -393,16 +394,6 @@ impl Drop for TestContextFFI {
); );
} }
println!("Hello 14");
let indexer_handle = unsafe { indexer_ffi.handle() };
if !indexer_handle.is_healthy() {
error!("Indexer handle has unexpectedly stopped before TestContext drop");
}
println!("Hello 15");
let container = bedrock_compose let container = bedrock_compose
.service(BEDROCK_SERVICE_WITH_OPEN_PORT) .service(BEDROCK_SERVICE_WITH_OPEN_PORT)
.unwrap_or_else(|| { .unwrap_or_else(|| {
@ -420,8 +411,6 @@ impl Drop for TestContextFFI {
container.id() container.id()
); );
} }
println!("Hello 16");
} }
} }
@ -429,33 +418,39 @@ impl Drop for TestContextFFI {
pub struct BlockingTestContextFFI { pub struct BlockingTestContextFFI {
ctx: Option<TestContextFFI>, ctx: Option<TestContextFFI>,
runtime: Arc<tokio::runtime::Runtime>, runtime: Arc<tokio::runtime::Runtime>,
indexer_ffi: IndexerServiceFFI,
} }
impl BlockingTestContextFFI { impl BlockingTestContextFFI {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let runtime = tokio::runtime::Runtime::new().unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap();
let runtime_wrapped = Arc::new(runtime); let runtime_wrapped = Arc::new(runtime);
let ctx = TestContextFFI::new(runtime_wrapped.clone())?; let (ctx, indexer_ffi) = TestContextFFI::new(&runtime_wrapped)?;
Ok(Self { Ok(Self {
ctx: Some(ctx), ctx: Some(ctx),
runtime: runtime_wrapped, runtime: runtime_wrapped,
indexer_ffi,
}) })
} }
#[must_use]
pub const fn ctx(&self) -> &TestContextFFI { pub const fn ctx(&self) -> &TestContextFFI {
self.ctx.as_ref().expect("TestContext is set") self.ctx.as_ref().expect("TestContext is set")
} }
pub fn runtime(&self) -> Arc<tokio::runtime::Runtime> { #[must_use]
self.runtime.clone() pub const fn runtime(&self) -> &Arc<tokio::runtime::Runtime> {
&self.runtime
} }
} }
impl Drop for BlockingTestContextFFI { impl Drop for BlockingTestContextFFI {
fn drop(&mut self) { fn drop(&mut self) {
let Self { ctx, runtime } = self; let Self {
ctx,
println!("Hello 20"); runtime,
indexer_ffi,
} = self;
// Ensure async cleanup of TestContext by blocking on its drop in the runtime. // Ensure async cleanup of TestContext by blocking on its drop in the runtime.
runtime.block_on(async { runtime.block_on(async {
@ -464,6 +459,12 @@ impl Drop for BlockingTestContextFFI {
} }
}); });
println!("Hello 21"); let indexer_handle =
// SAFETY: lib function ensures validity of value.
unsafe { indexer_ffi.handle() };
if !indexer_handle.is_healthy() {
error!("Indexer handle has unexpectedly stopped before TestContext drop");
}
} }
} }

View File

@ -7,7 +7,10 @@ use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use indexer_service_rpc::RpcClient as _; use indexer_service_rpc::RpcClient as _;
use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id, test_context_ffi::BlockingTestContextFFI}; use integration_tests::{
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id,
test_context_ffi::BlockingTestContextFFI,
};
use log::info; use log::info;
use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand}; use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcommand};
@ -149,29 +152,17 @@ async fn indexer_state_consistency() -> Result<()> {
#[test] #[test]
fn indexer_test_run_ffi() -> Result<()> { fn indexer_test_run_ffi() -> Result<()> {
println!("Hello 1");
let blocking_ctx = BlockingTestContextFFI::new()?; let blocking_ctx = BlockingTestContextFFI::new()?;
let runtime_wrapped = blocking_ctx.runtime(); let runtime_wrapped = blocking_ctx.runtime();
log::info!("Hello 3");
// RUN OBSERVATION // RUN OBSERVATION
runtime_wrapped.block_on(async { runtime_wrapped.block_on(async {
tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await;
}); });
println!("Hello 11"); let last_block_indexer = blocking_ctx
.ctx()
let last_block_seq = blocking_ctx.ctx().get_last_block_sequencer(runtime_wrapped.clone())?; .get_last_block_indexer(runtime_wrapped)?;
println!("Hello 12");
let last_block_indexer = blocking_ctx.ctx().get_last_block_indexer(runtime_wrapped)?;
println!("Hello 13");
println!("Last block on ind now is {last_block_indexer}");
println!("Last block on seq now is {last_block_seq}");
assert!(last_block_indexer > 1); assert!(last_block_indexer > 1);