Files
explorer/ffi/Cargo.toml
zooqueen f3bf55b0b9 feat(ffi): run Blockscout-rs explorer services in-process via cgo (single binary)
Make the explorer ONE binary that runs the Rust Blockscout-rs services
in-process via FFI — not by rewriting them in Go and not by exec'ing
subprocesses. The real Rust service entrypoints run on their own Tokio
runtime threads inside the Go binary.

- ffi/ — one C-ABI staticlib (crate-type=["staticlib"]) wrapping the
  service crates by path (../../explorer-rs/<svc>). One staticlib = one
  Rust std/panic runtime, no duplicate-symbol conflicts. A `service!`
  macro is the single place that bridges a service: parse Settings JSON,
  spawn a dedicated OS thread with a multi-thread Tokio runtime, run the
  service's real entrypoint. Exports lux_explorer_start_<svc>() +
  lux_explorer_start_all(). Disabled services still export their symbol
  and return LUX_FFI_ERR_DISABLED so the Go side links identically.
  sig-provider is wired and proven first (stateless, fastest to build);
  the other four services' deps/features/service! blocks are written out
  commented with confirmed entrypoint signatures for mechanical scale-out.

- ffi_on.go (//go:build ffi) — cgo preamble (-llux_explorer_ffi + the
  darwin/linux system libs the Rust graph needs) + extern decls;
  startFFIServices() launches the services at startup from main().
- ffi_off.go (//go:build !ffi) — no-op fallback so plain `go build ./...`
  compiles WITHOUT the staticlib present.

- Makefile `make single` = cargo build --release in ffi/ then
  go build -tags ffi. ffi/README.md documents the C ABI, the protoc /
  protoc-gen-openapiv2 build-tool prereqs, the seeded Cargo.lock, and the
  one-service-at-a-time scale-out recipe.

Verified: default `go build ./...` exits 0; ffi/ staticlib compiles with
sig-provider linked (.a 7.9MB shim -> 58MB with service); cgo -tags ffi
links it into the binary (symbols present via nm); the single binary boots
and logs `[ffi] start sig-provider -> ok (0)` with the service running
in-process. go vet clean under both build tags.
2026-06-23 21:06:50 -07:00

71 lines
3.6 KiB
TOML

[package]
name = "lux_explorer_ffi"
version = "0.1.0"
edition = "2021"
publish = false
description = "C-ABI staticlib that runs the Blockscout-rs explorer services in-process inside the Lux explorer Go binary (cgo). One Rust std copy wraps every needed service so the Go binary links a single .a."
# staticlib => a single self-contained `liblux_explorer_ffi.a` that cgo links
# directly into the explorer Go binary. We deliberately use ONE staticlib for
# ALL wrapped services (not one .a per service) so there is exactly one copy of
# the Rust std / panic runtime and no duplicate-symbol conflicts at link time.
[lib]
name = "lux_explorer_ffi"
crate-type = ["staticlib"]
[dependencies]
# Shared runtime glue. Tokio is built per-service-thread (see src/lib.rs); these
# are the only crates the FFI shim itself needs. anyhow is the common error type
# every wrapped service entrypoint returns; tokio drives the per-service runtime.
libc = "0.2"
serde_json = "1"
once_cell = "1"
anyhow = "1"
tokio = { version = "1", features = ["rt-multi-thread"] }
# --- Wrapped services (path deps into the sibling explorer-rs checkout) -------
# explorer-rs lives at ../../explorer-rs relative to this crate
# (/Users/a/work/lux/explorer/ffi -> /Users/a/work/lux/luxfi/explorer-rs).
# Each service is its OWN cargo workspace with no root workspace, so a plain
# path dep on the `*-server` crate pulls exactly that service's dependency
# graph. Each is feature-gated so a default `cargo build` (no --features) builds
# only the FFI shim + stubs and stays fast; turn services on individually.
#
# sig-provider is wired and proven first: it is stateless (no DB, no
# migrations), so it links and runs with the least build cost. Adding another
# service is a two-line change: (1) add its path dep below under a matching
# feature, (2) add a `service!{...}` entry in src/lib.rs. See README.md.
sig-provider-server = { path = "../../explorer-rs/sig-provider/sig-provider-server", optional = true }
# The remaining services are listed (commented) with their exact paths so
# scaling out is mechanical. They are NOT enabled by default because each pins a
# DIFFERENT major of blockscout-service-launcher (0.13/0.17/0.19/0.21) and pulls
# DB/migration stacks (sea-orm) — combining all of them inflates build time and
# must be done one at a time, verifying the link after each. See README.md
# "Scaling out" for the exact recipe.
#
# smart-contract-verifier-server = { path = "../../explorer-rs/smart-contract-verifier/smart-contract-verifier-server", optional = true }
# stats-server = { path = "../../explorer-rs/stats/stats-server", optional = true }
# multichain-aggregator-server = { path = "../../explorer-rs/multichain-aggregator/multichain-aggregator-server", optional = true }
# visualizer-server = { path = "../../explorer-rs/visualizer/visualizer-server", optional = true }
[features]
# Default = no service compiled in: the staticlib still builds (fast) and every
# entrypoint returns LUX_FFI_ERR_DISABLED so the Go side links and degrades
# gracefully. `make single` turns on the services it wants.
default = []
sig-provider = ["dep:sig-provider-server"]
# smart-contract-verifier = ["dep:smart-contract-verifier-server"]
# stats = ["dep:stats-server"]
# multichain-aggregator = ["dep:multichain-aggregator-server"]
# visualizer = ["dep:visualizer-server"]
# Convenience: every wired service at once.
all = ["sig-provider"]
[profile.release]
# Smaller, faster-linking staticlib. LTO off keeps incremental builds sane while
# scaling services out; flip to "thin" for a production single binary.
lto = false
codegen-units = 16