aggkit v0.11.0-rc11
Changes since v0.11.0-rc10 (11 commits).
🚀 What's New
- SQLite-backed cache for the bridge tracker (#1822, #1852): a new
bridgetracker/dbpackage can back the tracker's supervised-bridges registry and the activity cache with SQLite instead of pure in-memory, so already-resolved bridges survive a proxy restart. Opt-in viaTracker.DBPath— see Configuration Updates. Pruning/retention is still a no-op on the SQLite adapter for now (rows accumulate unboundedly once enabled). - Tracker step dates now come from on-chain facts, not "now" (#1864): each step's
start_date/end_dateis derived from its own deterministic on-chain fact (block timestamps, claim tx, settlement tx, etc.) where one exists, instead of always being stamped with the poll tick's timestamp. Also fixes a latent nil-pointer bug inUpdateStep. - Concurrency for tracker/activity scans (#1844): the activity engine now scans up to
MaxConcurrentNetworkScansnetworks in parallel (default 10), and the tracker's poll tick resolves up toMaxConcurrentResolutionsactive bridges at once (default 50) instead of one at a time. Also makes the per-call bridge-service HTTP timeout configurable and fixes the "skip step" fallback incorrectly overwriting a step's real error type. - Bridge tracker resilience once a bridge is already claimed (#1839): if an intermediate step can no longer resolve its historical fact (e.g. pruned RPC history), the tracker now falls back to the destination network's own
isClaimed()check and marks unresolvable prior steps asSkippedinstead of getting permanently stuck inTrackingStatusError. - Error message redaction (#1845, part of #1862): raw backend URLs,
host:port, and bare IPs (sometimes carrying API keys) are no longer echoed into client-facing fields (ErrorStep.Description,ErrorData.Message,CertificateData.Error, activity endpoint errors/warnings, proxy JSON error bodies). Redaction happens automatically — no config flag. - Gated network auto-registration (#1855, part of #1862):
bridgeservicefinderpreviously served any network discovered afterStartimmediately. NewAutoRegisterNewNetworksflag lets operators freeze the served set at start; late-discovered networks are recorded as pending and surfaced on the health endpoint instead. Defaults totrue(today's behavior). - Per-destination autoclaim cursors / stricter bridge-service health / stricter emitter verification (#1651, #1689, #1751 via #1854):
autoclaim_ler_cursoris now keyed by(source_network, destination_network)— a claimer added later no longer inherits or corrupts another destination's progress. Ships with migrationautoclaim0003.- Bridge-service
/healthnow reports per-source sync status (L1 info tree, GERs, claims) instead of pure liveness, backed by a short-lived cache and bounded compute timeout. Additive — still always returns HTTP 200. - A
BridgeEvent-shaped log is now rejected unless it's confirmed to come from the network's real bridge contract (resolved fromTracker.BridgeAddrs, or via the bridge service finder when the network is absent from that map). Previously any contract emitting a matching log was silently accepted.
- Independent L1 finality knobs + claim trimming (#1846, #1847 via #1849): fixes
L1InfoTreeSync.Finality()incorrectly returning the multidownloader'sBlockFinalityinstead of its own, which caused the aggsender startup guard to reject valid mixed-finality configs. Also restores trimming (instead of a hard error) for claims whose GER exists on L1 but isn't yet under the selected L1 info root — needed for the relaxed/conservative finality combination this PR enables.
⚠️ Breaking Changes
- 🔌 API/CLI:
bridgeservicefinder.Findergains aPendingNetworks() []PendingNetworkmethod — a compile-time breaking change for any out-of-repo implementer of the interface. All in-repo implementers (proxy, tracker, autoclaim, test doubles) are already updated.types.HealthResponsegains an optionalpending_networksfield, which is backwards compatible for existing clients. - 🔌 Behavioral: a network absent from
Tracker.BridgeAddrsnow must resolve its bridge address viabridgeservicefinder.Finder.BridgeAddressto have itsBridgeEventlogs accepted; previously a matching log from any emitter was accepted for such networks. A genuine mismatch now correctly rejects the tx asErrBridgeTxNotABridge. - No behavior change at default settings for the redaction or auto-registration changes.
📋 Configuration Updates
aggkit (main binary)
- New
AutoRegisterNewNetworksunder[AutoClaim.BridgeServiceFinder]. Defaulttrue(no behavior change). Withfalse, networks discovered after startup are recorded as pending instead of being served live. (#1862)
[AutoClaim.BridgeServiceFinder]
HealthCheckPath = "/"
HealthCheckTimeout = "5s"
RequireAllHealthyOnStart = false
AutoRegisterNewNetworks = true # <-- new
IgnoreNetworkIDs = []proxy — proxy/config/default.go
[BridgeServiceFinder]gainsAutoRegisterNewNetworks(also overridable via env varCDK_PROXY_BRIDGESERVICEFINDER_AUTOREGISTERNEWNETWORKS). (#1862)[Tracker]gainsDBPath,MaxConcurrentResolutions,ActivityPollInterval,ActivityRegisterResolveTimeout,ActivityMaxConcurrentRefreshes, and changes the default ofL2InjectionLookbackBlocks.BridgeAddrsis unchanged as a field but is now an override on top of bridge-service-finder resolution rather than the sole source of truth (see Breaking Changes). (#1822/#1852, #1844, #1854)[Tracker.ActivitySourceBridgeService]gainsTimeoutandMaxConcurrentNetworkScans. (#1844)
Full example (proxy/config/default.go, only the sections with changes shown):
[BridgeServiceFinder]
HealthCheckPath = "/"
HealthCheckTimeout = "5s"
RequireAllHealthyOnStart = false
AutoRegisterNewNetworks = true # <-- 🆕 New: freeze the served network set at startup when false; late-discovered networks are recorded as pending instead of served live
IgnoreNetworkIDs = []
[Tracker]
RetentionPeriod = "10m"
IdleTimeout = "30m"
ActivityIdleTimeout = "30m"
L1BlockFinality = "LatestBlock"
L2BlockFinality = "LatestBlock"
MaxTrackedBridges = 100000
L2InjectionLookbackBlocks = 10000000 # <-- 🔄 Changed (was 1000): how many blocks the L2GlobalExitRootAddress fallback scans backwards before giving up
DBPath = "" # <-- 🆕 New: SQLite file for the supervised-bridges registry; empty keeps the in-memory adapter (opt-in, don't point at a shared/world-writable dir like /tmp)
MaxConcurrentResolutions = 50 # <-- 🆕 New: how many active bridges the engine's poll tick resolves at once
ActivityPollInterval = "30s" # <-- 🆕 New: how often the activity engine refreshes every supervised from_address in the background
ActivityRegisterResolveTimeout = "10s" # <-- 🆕 New: how long a freshly registered from_address's first request waits for the engine's immediate refresh before answering
ActivityMaxConcurrentRefreshes = 10 # <-- 🆕 New: how many supervised addresses the activity engine's poll tick refreshes at once
[Tracker.ActivitySourceBridgeService]
PageSize = 100
Timeout = "30s" # <-- 🆕 New: per-HTTP-call timeout for the activity source's bridge-service client (previously hardcoded to 30s)
MaxConcurrentNetworkScans = 10 # <-- 🆕 New: how many networks GET /activity/from/{address} scans at once🔌 API Updates
Bridge tracker REST API (GET /tracker/v1/*) — CurrentAPIRevision bumped from 2 → 5 (bridgetracker/types/health.go):
- rev 3 — new step status value
"skipped"(StepStatusSkipped) and error type"skipped"(StepErrorSkipped): a step that can no longer be verified, but whose bridge was independently confirmed on the destination network (ClaimChecker.IsClaimed), now moves toskippedinstead of getting stuck inerror. (#1839) - rev 4 — corrects rev 3: the step that actually triggered the fallback keeps its own real
error_type(transient/permanent) instead of always being overwritten to"skipped"; other steps skipped alongside it that were never attempted now omiterrorentirely instead of carrying a placeholder. (#1844) - rev 5 —
GET /tracker/v1/healthgains two fields, both optional/backwards-compatible:start_date(RFC3339, the instant the instance started)pending_networks[](networks discovered after startup and not activated becauseAutoRegisterNewNetworks = false; omitted when empty)
(#1862)
// GET /tracker/v1/health (example, rev 5)
{
"status": "ok",
"instance_id": "e3b0c442-...",
"start_date": "2026-09-23T08:00:00Z",
"config_sha1": "a1b2c3...",
"version": { "...": "..." },
"pending_networks": [
{
"network_id": 3,
"rollup_address": "0x...",
"block_number": 12345,
"first_seen": "2026-09-23T08:05:00Z",
"reason": "AutoRegisterNewNetworks=false: network appeared after Start"
}
]
}Other tracker API changes (not their own revision bump, folded into rev 5 / content-only changes to existing fields):
- Automatic error redaction:
ErrorData.message,ErrorStep.description[]andCertificateData.errorno longer expose raw URLs/host:port/IPs — they're redacted in the type's ownMarshalJSONbefore leaving over HTTP/WebSocket. (#1845) - New
StepErrorTypevalue"warning"(StepErrorWarning): the step completed normally (status: done) but some optional piece of data could not be resolved deterministically (e.g. the L2 GER-injection block); not a failure. (#1864) LERUpdateResultgainsblock_timestamp(previously onlynetwork_id/ler/block_number).GET /activity/from/{from_address}can now return503with aRetry-Afterheader (seconds =ActivityPollInterval) when a freshly registered address's first refresh isn't ready yet — previously this blocked the response untilActivityRegisterResolveTimeout; now, once that elapses, it answers 503 instead of empty data. (#1844)bridgeservicefinder.Finder(Go package, not HTTP) gains thePendingNetworks() []PendingNetworkmethod — breaks the interface for any out-of-repo implementer (see Breaking Changes). (#1862)
Aggkit bridge service API (GET /bridge/v1/health, bridgeservice/types.HealthCheckResponse) — additive, still always returns HTTP 200:
- New
sync_status:"done"/"pending"/"error". - New
detailswith a per-component summary (l1,l2,l2_ger), each withis_active,is_synced(optional) anderror(optional). (#1854, part of #1689)
// GET /bridge/v1/health (example)
{
"status": "ok",
"time": "2026-09-23T08:00:00Z",
"version": "v0.11.0-rc11",
"sync_status": "pending",
"details": {
"l1": { "is_active": true, "is_synced": false },
"l2": { "is_active": true, "is_synced": true },
"l2_ger": { "is_active": true }
}
}Swagger docs regenerated for both services: bridgetracker/api/docs/*, bridgeservice/docs/* (make generate-swagger-docs).
✅ Testing & Validation
New/expanded e2e coverage:
test/e2e/proxy_autoregister_test.go(TestProxyAutoRegisterNewNetworks, #1855)test/e2e/proxy_tracker_redaction_test.go(error redaction, #1845)test/e2e/finality_knobs_test.go(independent L1 finality knobs, #1846/#1847)test/e2e/autoclaim_test.go(per-destination autoclaim cursors, #1651)test/e2e/health_test.go(bridge-service/healthsync status, #1689)test/e2e/proxy_tracker_test.go— extended for the emitter-verification fix (#1751)
Plus extensive new/updated unit tests across bridgetracker/db, bridgetracker/domain, bridgetracker/sources, bridgeservicefinder, autoclaim/bridgedetector, and common/redact_test.go.
📦 Full Changelog
| PR | Summary |
|---|---|
| #1862 | fix(bridgetracker): redact error URLs + gate bridgeservicefinder auto-registration |
| #1864 | feat(bridgetracker): step start_date/end_date from on-chain facts, not now |
| #1852 | feat(bridgetracker): add SQLite-backed cache for tracker/activity state (#1822) |
| #1854 | fix: per-destination autoclaim cursors, bridge-service health sync status, bridgetracker emitter verification (#1651, #1689, #1751) |
| #1844 | feat(bridgetracker): parallelize activity/tracker scans and make bridge-service timeout configurable. Fix skip step |
| #1849 | fix: make L1 finality knobs independent — Finality() passthrough (#1846) and claim trimming (#1847) |
| #1839 | fix(bridgetracker): skip unverifiable intermediate steps once a bridge is already claimed |
| #1857, #1858, #1859, #1860 | ci: pin/fix Claude Interactive workflow (claude-code-action version, model pin, toggling show_full_output)
|
Full Changelog: v0.11.0-rc10...v0.11.0-rc11