feat/1015-versioned-chain-adapter-registry - #1231
Merged
Mosas2000 merged 1 commit intoAug 29, 2026
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Schema —
backend/src/database/migrations/20260829010000_versioned_chain_adapter_registry.tschain_adapter_signers— trusted signer public keys (ed25519 / secp256k1 / p256)chain_adapters— one row per epoch; uniqueregistry_version(chain:identity:epoch);partial-unique index enforces exactly one
activeepoch per(chain_id, contract_identity)chain_adapter_quarantine— quarantined ingestion with reason + raw logService —
backend/src/services/chainAdapterRegistry.service.tscanonicalizeAbi/computeAbiHash(order- andwhitespace-independent),
adapterFingerprint,verifyAdapterSignature,decodeLogWithAbi(ethers, bigint→string, stable arg names),resolveEpochForBlockstageAdapter(signature verified against a registered signerbefore the row is written) →
activateAdapter(closes the prior epoch's blockrange, marks it
superseded) →rollbackAdapter(marks the epochrolled_back, re-opens the previoussupersededepoch asactive)recordProxyUpgrade— always stages a new epoch from the upgrade blockdecodeHistoricalLog(registryVersion, rawLog)— reproducible decodevalidateAndRouteIngestion(...)— resolves the epoch for the block, comparesobserved bytecode / ABI hash, and quarantines (reasons
unknown_bytecode,abi_change,no_active_adapter,out_of_range,decode_failure) instead ofdecoding against a stale adapter
HTTP API —
backend/src/api/routes/chainAdapterRegistry.routes.ts, mounted at/api/v1/chain-adapters(via the operational-monitoring route group):GET /:chainId/:contractIdentity/epochsGET /quarantinePOST /decodePOST /validate(409 on quarantine)POST /signers·POST /adapters·POST /adapters/:id/activate·POST /adapters/:id/rollback·POST /proxy-upgrades(allconfig:write)Fixtures & CI —
backend/src/services/chainAdapters/fixtures/{ethereum,polygon,base}.jsonsigned adapters + decoded-log samples, generated with real ethers ABI encoding.
Acceptance criteria
validateAndRouteIngestionwrites achain_adapter_quarantinerow instead of decodingrecordProxyUpgradestages a new epoch from the upgrade block, appends toproxy_historydecodeHistoricalLogloads that exact epoch's ABI and decodes deterministicallychainAdapterRegistry.fixtures.test.tsiteratesSUPPORTED_CHAINSin the existing backend test stepTests
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 supportedchain: 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/tscwere not runin this environment (no
node_modulesin the working tree);strictis off intsconfigand eslint hasno-explicit-any/no-unused-varsoff.Follow-ups (not in this PR)
validateAndRouteIngestion()from the EVM watchers(
wormholeWatcher.service.ts,bridgeMonitor.worker.ts) to gate live ingestiondecodeLogWithAbi—the schema already carries
event_schemas