Skip to content

feat/1015-versioned-chain-adapter-registry - #1231

Merged
Mosas2000 merged 1 commit into
StellaBridge:mainfrom
LamsOfJos:feat/1015-versioned-chain-adapter-registry
Aug 29, 2026
Merged

feat/1015-versioned-chain-adapter-registry#1231
Mosas2000 merged 1 commit into
StellaBridge:mainfrom
LamsOfJos:feat/1015-versioned-chain-adapter-registry

Conversation

@LamsOfJos

Copy link
Copy Markdown
Contributor

feat(backend): versioned chain adapter & ABI compatibility registry

Closes #1015

Problem

ABIs, event layouts, proxy implementations and chain-specific semantics evolve.
A changed decoder can silently reinterpret historical logs or accept an
incompatible contract as the configured bridge.

What this implements

A signed, versioned chain-adapter registry. Each adapter epoch is an
immutable record of a contract's decoding contract at a point in its history:
contract identity, ABI hash, deployment block range, decimals, proxy
implementation history, event schemas and an optional migration handler.
Historical logs are routed through the epoch that was in force at the log's
block height.

Schemabackend/src/database/migrations/20260829010000_versioned_chain_adapter_registry.ts

  • chain_adapter_signers — trusted signer public keys (ed25519 / secp256k1 / p256)
  • chain_adapters — one row per epoch; unique registry_version (chain:identity:epoch);
    partial-unique index enforces exactly one active epoch per (chain_id, contract_identity)
  • chain_adapter_quarantine — quarantined ingestion with reason + raw log

Servicebackend/src/services/chainAdapterRegistry.service.ts

  • Pure, reproducible helpers: canonicalizeAbi / computeAbiHash (order- and
    whitespace-independent), adapterFingerprint, verifyAdapterSignature,
    decodeLogWithAbi (ethers, bigint→string, stable arg names), resolveEpochForBlock
  • Lifecycle: stageAdapter (signature verified against a registered signer
    before the row is written) → activateAdapter (closes the prior epoch's block
    range, marks it superseded) → rollbackAdapter (marks the epoch
    rolled_back, re-opens the previous superseded epoch as active)
  • recordProxyUpgrade — always stages a new epoch from the upgrade block
  • decodeHistoricalLog(registryVersion, rawLog) — reproducible decode
  • validateAndRouteIngestion(...) — resolves the epoch for the block, compares
    observed bytecode / ABI hash, and quarantines (reasons unknown_bytecode,
    abi_change, no_active_adapter, out_of_range, decode_failure) instead of
    decoding against a stale adapter

HTTP APIbackend/src/api/routes/chainAdapterRegistry.routes.ts, mounted at
/api/v1/chain-adapters (via the operational-monitoring route group):

  • GET /:chainId/:contractIdentity/epochs
  • GET /quarantine
  • POST /decode
  • POST /validate (409 on quarantine)
  • POST /signers · POST /adapters · POST /adapters/:id/activate ·
    POST /adapters/:id/rollback · POST /proxy-upgrades (all config:write)

Fixtures & CIbackend/src/services/chainAdapters/fixtures/{ethereum,polygon,base}.json
signed adapters + decoded-log samples, generated with real ethers ABI encoding.

Acceptance criteria

Criterion Status
Unknown bytecode or ABI changes quarantine ingestion validateAndRouteIngestion writes a chain_adapter_quarantine row instead of decoding
Proxy upgrades create explicit adapter epochs recordProxyUpgrade stages a new epoch from the upgrade block, appends to proxy_history
Decoding is reproducible from registry version + raw log decodeHistoricalLog loads that exact epoch's ABI and decodes deterministically
Operators can stage and roll back an adapter without data loss ✅ stage → activate → rollback moves only the "current decoder" pointer; historical rows are never mutated
CI verifies fixtures across every supported chain chainAdapterRegistry.fixtures.test.ts iterates SUPPORTED_CHAINS in the existing backend test step

Tests

  • backend/tests/services/chainAdapterRegistry.service.test.ts — hash stability,
    ed25519 signature round-trip + tamper rejection, decode reproducibility, epoch
    resolution, staging rejects unsigned / unknown-signer / bad-signature adapters
  • backend/tests/services/chainAdapterRegistry.fixtures.test.ts — per supported
    chain: fixture present, metadata self-consistent, every sample decodes to its
    expected event/args, decoding is reproducible

Fixture decoding was verified against ethers 6.16. vitest / tsc were not run
in this environment (no node_modules in the working tree); strict is off in
tsconfig and eslint has no-explicit-any / no-unused-vars off.

Follow-ups (not in this PR)

  • Call validateAndRouteIngestion() from the EVM watchers
    (wormholeWatcher.service.ts, bridgeMonitor.worker.ts) to gate live ingestion
  • Soroban (schema-based, non-EVM) decoder path alongside decodeLogWithAbi
    the schema already carries event_schemas

@Mosas2000
Mosas2000 merged commit 30abe12 into StellaBridge:main Aug 29, 2026
16 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.

feat: Create Versioned Chain Adapter and ABI Compatibility Registry

2 participants