mirror of
https://github.com/luxfi/explorer.git
synced 2026-09-24 00:25:12 +00:00
Make the explorer ONE Go binary fronted by github.com/hanzoai/zip (Fiber v3 /
fasthttp) that runs the Blockscout-rs Rust services IN-PROCESS via the existing
cgo FFI staticlib and reverse-proxies to each. Least-work: the Rust services are
not rewritten/ported — their real entrypoints run on Tokio threads inside the
process, keeping their own HTTP servers on loopback.
Front router (front.go): zip owns the public listener; routes in order
/api/<prefix>/* -> httputil.ReverseProxy -> 127.0.0.1:<svc_port> (prefix stripped)
/* -> the existing net/http explorer mux via zip.AdaptNetHTTP
Security headers + CORS become zip middleware (replaces withSecurity).
Config (services: block, ServicesConfig/resolveServices in config.go+services.go):
single source of truth for enabled services, loopback ports (base 8050,
HTTP=base+2n/gRPC=+1), URL prefixes, and per-service settings JSON; both the
proxy and the FFI launcher read it. startFFIServices now takes resolvedService
and feeds each service its server addr (+ DB url when set).
Status: sig-provider WIRED + PROVEN end-to-end — one process owns :8090 and
:8050, curl /api/sig/health -> {"status":"SERVING"} and the real REST API
/api/sig/api/v1/abi/function -> 200. visualizer + smart-contract-verifier are
stateless and ready to wire (recipe in ffi/README.md). stats +
multichain-aggregator stay DISABLED: blockscout-service-launcher's DB layer is
Postgres-only (initialize_postgres pins DatabaseBackend::Postgres) — no SQLite
without a Rust rewrite — and stats also needs a populated Blockscout indexer
Postgres. The Go side already passes database.url+migrations through, so they
enable with config alone once a Postgres exists.
Build: make single (cargo build ffi/ + go build -tags ffi) unchanged; default
go build ./... is the Go-native binary (ffi_off.go stub, no cgo). go build ./...
and go test ./... pass; make single links the sig-provider symbol and boots.
103 lines
4.0 KiB
Go
103 lines
4.0 KiB
Go
//go:build ffi
|
|
|
|
// Package main — FFI build variant.
|
|
//
|
|
// When built with `-tags ffi` the explorer links the Rust staticlib
|
|
// `liblux_explorer_ffi.a` (produced by `cargo build --release` in ./ffi) and
|
|
// runs the Blockscout-rs explorer services (sig-provider, and — as they are
|
|
// scaled out — smart-contract-verifier, stats, multichain-aggregator,
|
|
// visualizer) IN-PROCESS via cgo. No subprocesses, no Go rewrites: each
|
|
// service's real Rust entrypoint runs on its own Tokio runtime thread inside
|
|
// this single binary.
|
|
//
|
|
// Build: make single (cargo build --release in ffi/ then go build -tags ffi)
|
|
// The non-FFI fallback lives in ffi_off.go (//go:build !ffi) so a plain
|
|
// `go build ./...` compiles WITHOUT the staticlib present.
|
|
package main
|
|
|
|
/*
|
|
#cgo LDFLAGS: -L${SRCDIR}/ffi/target/release -llux_explorer_ffi
|
|
// System libraries the Rust staticlib (tokio + reqwest/curl + openssl + ring)
|
|
// transitively needs. macOS frameworks cover Security/TLS + reachability;
|
|
// resolv/c++ cover DNS + the C++ runtime pulled by curl/openssl. On Linux this
|
|
// preamble is replaced by ffi_on_linux.go's cgo line.
|
|
#cgo darwin LDFLAGS: -lresolv -lc++ -framework Security -framework CoreFoundation -framework SystemConfiguration
|
|
#cgo linux LDFLAGS: -lresolv -lstdc++ -lm -ldl -lpthread -lssl -lcrypto
|
|
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
|
|
// Mirrors the C ABI exported by ffi/src/lib.rs. Each starter spawns the named
|
|
// service on its own runtime thread and returns 0 (LUX_FFI_OK) once launched.
|
|
extern int lux_explorer_start_sig_provider(const char* config_json);
|
|
extern int lux_explorer_start_all(void);
|
|
*/
|
|
import "C"
|
|
|
|
import (
|
|
"log"
|
|
"unsafe"
|
|
)
|
|
|
|
// ffiEnabled is read by main.go to log which build variant is running.
|
|
const ffiEnabled = true
|
|
|
|
// ffiResult maps the C return codes from ffi/src/lib.rs to human strings.
|
|
func ffiResult(code C.int) string {
|
|
switch int(code) {
|
|
case 0:
|
|
return "ok"
|
|
case 1:
|
|
return "bad-config"
|
|
case 2:
|
|
return "parse-error"
|
|
case 3:
|
|
return "disabled"
|
|
case 4:
|
|
return "spawn-error"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
// ffiStarters maps an FFI service name to its C entrypoint. Adding a service
|
|
// is one line here plus its service!{…} block in ffi/src/lib.rs and its
|
|
// dep+feature in ffi/Cargo.toml (see ffi/README.md). Disabled services still
|
|
// export their symbol and return LUX_FFI_ERR_DISABLED, so this table can list
|
|
// every service unconditionally — the Go binary links identically regardless
|
|
// of which cargo features were compiled in.
|
|
var ffiStarters = map[string]func(*C.char) C.int{
|
|
"sig-provider": func(c *C.char) C.int { return C.lux_explorer_start_sig_provider(c) },
|
|
// Scale-out (uncomment as the matching service!{…} is enabled in ffi/):
|
|
// "smart-contract-verifier": func(c *C.char) C.int { return C.lux_explorer_start_smart_contract_verifier(c) },
|
|
// "stats": func(c *C.char) C.int { return C.lux_explorer_start_stats(c) },
|
|
// "multichain-aggregator": func(c *C.char) C.int { return C.lux_explorer_start_multichain_aggregator(c) },
|
|
// "visualizer": func(c *C.char) C.int { return C.lux_explorer_start_visualizer(c) },
|
|
}
|
|
|
|
// startFFIServices launches each resolved in-process Rust service by calling
|
|
// its C entrypoint with the settings JSON resolveServices rendered (server
|
|
// addr/port + optional DB url). It is called once from main() at startup in
|
|
// the ffi build; the non-FFI build's stub is a no-op.
|
|
//
|
|
// Each starter returns immediately after spawning the service's worker thread,
|
|
// so this does not block the zip HTTP listener.
|
|
func startFFIServices(svcs []resolvedService) {
|
|
for _, s := range svcs {
|
|
fn, ok := ffiStarters[s.Name]
|
|
if !ok {
|
|
log.Printf("[ffi] no entrypoint for service %q (rebuild ffi with its feature?)", s.Name)
|
|
continue
|
|
}
|
|
var cstr *C.char
|
|
if s.SettingsJSON != "" {
|
|
cstr = C.CString(s.SettingsJSON)
|
|
}
|
|
code := fn(cstr)
|
|
if cstr != nil {
|
|
C.free(unsafe.Pointer(cstr))
|
|
}
|
|
log.Printf("[ffi] start %s on :%d -> %s (%d)", s.Name, s.HTTPPort, ffiResult(code), int(code))
|
|
}
|
|
}
|