Skip to content

build(stylus): build and publish libstylus.so for leafage-evm - #37

Merged
littlehand26 merged 6 commits into
mainfrom
feat/stylus-cdylib
Jul 23, 2026
Merged

build(stylus): build and publish libstylus.so for leafage-evm#37
littlehand26 merged 6 commits into
mainfrom
feat/stylus-cdylib

Conversation

@littlehand26

@littlehand26 littlehand26 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

leafage-evm executes Stylus contracts by dlopening libstylus.so — the same
native runtime this repo already links into the node. Nothing previously built
that library for Linux, so leafage-evm could reach the Stylus dispatch but only
fall back to an Unconfigured revert.

Changes

  • Add cdylib to crates/stylus, allowing the workspace to produce
    libstylus.so. The existing prover_ffi re-export carries
    free_rust_bytes into the shared object.
  • Add Dockerfile.libstylus, producing a scratch COPY-only image containing
    only /libstylus.so.
  • Add a release-only multi-arch workflow for amd64 and arm64.

Release policy

The workflow runs only when a GitHub Release is published
(release.published). It checks out that release tag and publishes:

  • libstylus:amd64-<release-tag>
  • libstylus:arm64-<release-tag>
  • libstylus:<release-tag> as the immutable multi-arch manifest
  • libstylus:latest pointing to the same multi-arch manifest

Pull requests no longer assume the AWS role or push images. Each release keeps
an immutable source tag while updating latest for consumers that deliberately
follow the newest published libstylus.

Why a separate image

leafage-evm can copy the native runtime from either a pinned Nitro release or
the latest published release without vendoring or rebuilding this source tree.
The release tag remains available when an explicit Nitro version is required.

glibc

The builder uses bookworm (glibc 2.36), while leafage-evm runs on Ubuntu 24.04
(glibc 2.39). The published library requires at most GLIBC_2.34 on both
architectures and loads successfully in the consumer runtime.

Symbol check

The build checks the required exported symbol names after stripping:
stylus_activate, stylus_compile, stylus_call,
stylus_target_set, and free_rust_bytes. Consumer integration tests remain
the ABI check because ELF symbol names do not encode function signatures or
repr(C) layouts.

Scope and verification

Only the Rust workspace, Brotli source, and required build script are copied
into the builder. The Nitro node Dockerfile and node release workflow are
unchanged.

Before switching the trigger to release-only, the multi-arch artifact at
f7c0ab5 was verified on amd64 and arm64: both libraries load on Ubuntu 24.04,
resolve their dynamic dependencies, and expose the required symbols.
leafage-evm integration has also reproduced the moduleHash and matched an Arb One
writer across real Stylus calls.

leafage-evm loads the stylus FFI at runtime via libloading
(LEAFAGE_ARB_STYLUS_LIB), which needs a shared object; the crate previously
built only lib + staticlib. The prover_ffi re-export already funnels
free_rust_bytes into the linked output, so the cdylib exports the full
stylus_* + free_rust_bytes symbol set with no further changes.
leafage-evm dlopens libstylus.so to execute Stylus contracts, but nothing
built it for Linux and its image shipped without it, so every Stylus call fell
back to a revert instead of running the WASM.

Adds a Dockerfile that builds the library and a workflow that publishes it to
public ECR as `libstylus:<rev>` (amd64 + arm64). The image is a scratch layer
holding just the .so — consumers do `COPY --from`, it is not meant to run.

Two things worth keeping in view:

- The builder sits on bookworm (glibc 2.36) because leafage-evm runs on
  ubuntu:24.04 (glibc 2.39), and a library built against an older glibc loads
  on a newer one but not the reverse. If that runtime base moves, this one has
  to stay at or below it.
- The build asserts the five symbols leafage resolves at runtime
  (stylus_activate/compile/call/target_set, free_rust_bytes), so ABI drift
  fails the build instead of failing at dlopen time in production.

Only the Rust workspace is copied into the build context; the Go tree is not
involved.
The release profile builds with debuginfo, leaving the library at ~237MB. That
would land whole in leafage-evm's image for no benefit — nothing consumes the
DWARF sections there. --strip-debug keeps the dynamic symbol table, which is
all leafage resolves against, and the symbol assertion now runs after the strip
so it covers what actually ships.
@littlehand26
littlehand26 merged commit 3085f2b into main Jul 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant