Skip to content

feat: bridge service add block_finalized field to several endpoints 1915 - #1921

Merged
joanestebanr merged 12 commits into
developfrom
feat/1915-block-finalized
Oct 26, 2026
Merged

joanestebanr merged 12 commits into
developfrom
feat/1915-block-finalized

Conversation

@joanestebanr

@joanestebanr joanestebanr commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

🔄 Changes Summary

This PR implements two bridge service issues: #1915 (block_finalized) and #1920 (api_revision).

#1915 — block_finalized

  • Add block_finalized (true | false | omitted) to the bridge service responses tied to a block, so clients know whether a response can still be affected by a reorg.
  • The finality comes from the syncer that served the data, not from comparing block numbers against the finalized block (a stored block can have been reorged and replaced by another one with the same number). Each syncer is asked IsBlockFinalized(blockNum, blockHash), using the block hash read together with the item:
    • L1InfoTreeSync in multidownloader mode: the IsFinal flag stored by the multidownloader, which is only set once the block was validated (the stored header must have the given hash).
    • BridgeSync, ClaimSync, L2GERSync and L1InfoTreeSync in legacy mode (no finality flag): the reorg detector keeps tracking every block that was not final when processed, and only stops tracking it once its hash matched the canonical chain below the finalized block. So a stored block with the given hash that is no longer tracked is finalized. The logic lives in one function, reorgdetector.IsBlockFinalized, which asks the tracker first and reads the stored hash afterwards: the reorg detector only drops a reorged block from its tracker after the syncer has deleted it, so with that order an orphaned block can never be reported as finalized. An unknown subscriber id is an error (reorgdetector.ErrSubscriberNotFound), never "finalized".
  • The field is omitted (unknown) when the syncer can't tell: no/zero hash, the block is not stored with that hash (e.g. replaced after the item was read), block without stored hash (rows synced before the column existed), or a syncer error.
  • Bridge, Claim, GlobalExitRootInfo, L1InfoTreeLeaf and tree.Root carry the stored BlockHash (meddler:"-", not persisted). It is read in the same SELECT as the rows (LEFT JOIN block, through internal read models), so it is the hash of the block the item belonged to when it was read, even if a reorg is processed right after.

#1920 — api_revision in the health endpoint

  • GET / and GET /health return api_revision (HealthCheckResponse.APIRevision), backed by bridgeservice.CurrentAPIRevision (bridgeservice/revision.go), starting at 1. Same mechanism as the bridge tracker's, but an independent counter.
  • Purely informational, and always present whatever the sync_status (the endpoint still answers 200).
  • The rule for bumping it and the changes of each revision are documented in bridgeservice/docs/API_REVISIONS.md (also linked from docs/bridge_service.md). TestCurrentAPIRevisionHasHistoryEntry fails if the constant, the history in revision.go and that file get out of sync.
  • bridgeservicefinder's health checker only looks at the status code: a test covers a body with and without api_revision (and with unknown future fields).

⚠️ Breaking Changes

  • 🔌 API/CLI: none. The new field is optional/additive.

🔌 API Updates

🔌 Bridge service API

✅ Testing

  • 🤖 Automatic: tests of reorgdetector.IsBlockFinalized with the real reorg detector and a deterministic test of the call order, handler tests of api_revision in every sync_status case, the client round trip, the finder's health checker, and unit tests for L1InfoTreeSync, BridgeSync, ClaimSync and L2GERSync IsBlockFinalized (tracked / not tracked / hash mismatch / no hash / errors), the block hash filling in the queries, and the bridge service handlers (block_finalized_test.go).

🐞 Issues

📝 Notes

  • L2GERSync in Legacy mode reports it as unknown: the stored block number there is the polling head, not the real injection block.
  • Not done: a per-request cache of IsBlockFinalized results (one primary key read per row today); a batched API would change five interfaces.
  • The count and the page of GetBridgesPaged / GetClaimsPaged are still two statements, and bridge / bridge_archive are read in two statements (independent of the block hash).
  • L1InfoTreeSync.GetInfoByGlobalExitRoot doesn't fill the leaf BlockHash (no ctx, and no endpoint reporting block_finalized uses it).
  • bridgetracker swagger docs were not regenerated (the generator also produced unrelated diffs there).
  • Not run end to end against a real reorg detector on a reorging chain.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-26T11:08:19.124643Z 46d647f Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@joanestebanr
joanestebanr marked this pull request as draft October 23, 2026 14:54
@joanestebanr joanestebanr self-assigned this Oct 23, 2026
@joanestebanr
joanestebanr marked this pull request as ready for review October 26, 2026 11:05

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46d647f31d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread l1infotreesync/l1infotreesync.go Outdated
Comment on lines +259 to +262
if header == nil || (blockHash != (common.Hash{}) && header.Hash != blockHash) {
return false, false, nil
}
return isFinal, true, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require a block hash before reporting multidownloader finality

When blockHash is zero—such as for settlement rows whose block hash is unavailable—the condition skips hash validation and reports the finality of whichever block currently occupies blockNum. During a reorg, an orphaned row can therefore be labeled block_finalized: true based on its canonical replacement, defeating the identity check this field is intended to provide. Return found=false when no hash was supplied, as the legacy path already does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. I had left the zero-hash passthrough on purpose (assuming IsFinal alone was enough for the multidownloader path), but it does defeat the identity check. Fixed in 0734796: IsBlockFinalized now returns found=false for a zero hash in both modes, and in the multidownloader path the stored header must have the same hash as the one the caller read. The multidownloader keeps a single block per number in blocks, so without the hash is_final could belong to the block that replaced the one the row came from.

When can a zero hash reach here? For a block with events synced by the multidownloader, never: logs.block_number has a foreign key to blocks and the header is stored with the logs. The only zero-hash block (addLastBlockIfNotIncluded, a fake empty checkpoint) has no events, so no leaf or settlement lives in it. What is left are rows with a NULL hash in the l1infotreesync block table, written by the legacy syncer before that column existed; they now report unknown and block_finalized is omitted. The test "no hash given: unknown" covers it.

Comment thread bridgesync/processor.go Outdated
Comment on lines +556 to +558
if err = p.fillBlockHashes(ctx, bridges); err != nil {
return nil, 0, err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Read bridge rows and their block hashes atomically

If a reorg is processed after the bridge rows are scanned but before fillBlockHashes runs, this follow-up query can attach the replacement block's hash to a bridge from the orphaned block at the same height. IsBlockFinalized then validates the replacement rather than the returned bridge and can eventually expose stale data as finalized. Include the block hash in the original query or keep both reads in one consistent transaction; the analogous follow-up enrichment in claims, roots, leaves, and injected GER reads has the same consistency risk.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, and the same race existed in claims, roots, leaves and the injected GER lookup. Fixed in 500cd10: every one of them now reads the block hash in the same SELECT as the rows, with a LEFT JOIN block (a single statement, so a single snapshot). The public types keep BlockHash as a non-persisted field (meddler:"-"); each package scans into an internal read model with block_hash and converts, following the VerifiedBatchWithBlockHash precedent. I did not use a read transaction because the connection is opened with _txlock=immediate, which would take the write lock.

Covered: GetBridgesPaged, GetBridgeByDepositCount, GetBridgesByContent (bridge and bridge_archive), GetRootByLER, GetClaimsPaged (the join wraps the final select of the compaction CTE, so the compaction cases are untouched), GetClaimsByGER, L1InfoTreeSync.GetInfoByIndex and GetFirstGERAfterL1InfoTreeIndex. A block row without hash gives the zero hash, which IsBlockFinalized reports as unknown. Not covered, and independent of the hash: the count and the page are still two statements, and bridge / bridge_archive are read in two statements.

While doing it I found a related race in IsBlockFinalized itself (it read the stored hash before asking the reorg detector); see the summary comment, item 3.

@arnaubennassar

Copy link
Copy Markdown
Collaborator

@claude

@claude

claude Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Claude finished @arnaubennassar's task in 1m 2s —— View job


Review of PR #1921 (block_finalized + api_revision)

I read the full diff against origin/develop. I did not build the code or run the tests or the linter.

The design holds up. In the legacy path, untracked means finalized. I checked reorgdetector.detectReorgInTrackedList: it calls notifySubscriber and waits for the reorg to be processed before it drops the tracked range, and it only prunes blocks at or below the finalized block whose hash matches. The reorg detector IDs also match the subscription IDs in all four syncers (syncerID.String() in bridgesync and claimsync, syncerID in l1infotreesync, reorgDetectorID in l2gersync). The findings below are mostly about consistency between the PR description and the code, and about a couple of fail-open edges.


🟡 Suggested

1. Multidownloader path reports finality when no hash is given, which contradicts the PR description.
l1infotreesync/l1infotreesync.go, IsBlockFinalized, case d.blockFinality != nil:

if header == nil || (blockHash != (common.Hash{}) && header.Hash != blockHash) {
  • The PR description says the field is omitted for "no/zero hash". The legacy path does that, but this path skips the identity check when the hash is zero and returns the IsFinal of whatever block is stored at blockNum. A test (stored as finalized, no hash given) asserts this, so it is deliberate.
  • It is reachable in two places:
    • GetSettlementsHandler: v.BlockHash is nil when the block row has no hash.
    • GetInfoByIndex: it fills leaf.BlockHash with a zero hash when the block is not stored or has no hash.
  • If the original block was reorged and replaced, the response can say block_finalized: true for the replacement block. That defeats the identity check this field is built around. It is the same finding Codex raised.
  • Suggested fix: return found=false on a zero hash, as the legacy path and the other three syncers do, and flip that test to expect unknown.

2. The block hash is read in a separate query from the rows, so a reorg between the two reads can pair a row with the wrong hash.
bridgesync/processor.go (fillBlockHashes, called from GetBridgesPaged, GetBridgeByDepositCount and GetBridgesByContent), claimsync/storage (FillBlockHashes), bridgesync.GetRootByLER, l1infotreesync.GetInfoByIndex and l2gersync.GetFirstGERAfterL1InfoTreeIndex.

  • If a reorg is processed after the rows are scanned but before the hash query runs, the hash of the replacement block is attached to a row from the orphaned block. IsBlockFinalized then validates the replacement block, so stale data can be reported as finalized once that block is final.
  • The window is narrow, but the PR description says the hash is "read together with the item", which is not strictly true today.
  • Options: SELECT … LEFT JOIN block ON block.num = <table>.block_num to get hash in the same statement, or run the row read and the hash read in one read transaction. The settlements query already joins block.hash, so there is precedent.
  • Even a short comment on the assumption would help if you decide to accept the risk.

3. IsBlockFinalized fails open if the reorg detector doesn't know the subscriber ID.
ReorgDetector.GetTrackedBlockByBlockNumber returns db.ErrNotFound both when a block is untracked and when the subscriber ID has no tracker at all. All four implementations map ErrNotFound to finalized=true, so a mismatched ID or a syncer that never subscribed would report every block as finalized. The IDs are correct today. A small test that uses a real ReorgDetector with a tracked block would catch a regression, and it would also cover the "not run end to end against a reorg detector" gap in the PR notes.

4. Duplicated code.
The GetTrackedBlockByBlockNumber switch (tracked → false,true; ErrNotFound → true,true; other → error) is copied four times, in bridgesync, claimsync, l2gersync and l1infotreesync. getBlockHash is also copied three times with the same SQL (bridgesync, l1infotreesync, l2gersync), plus a fourth in claimsync storage. A shared helper, for example reorgdetector.IsBlockFinalized(rd, id, num) or one in sync, would remove the risk of the copies drifting apart (see item 3).

5. blockFinalized makes one reorg detector lookup per row.
GetBridgesHandler, GetClaimsHandler and the by-content and by-GER handlers call IsBlockFinalized per item. Each call is a DB query plus a reorg detector lock. Several items often share a block number, and pages can be large. Caching per (blockNum, blockHash) within a request is an easy win and is the same trick fillBlockHashes already uses.


🟢 Optional

  • bridgeservice/types/types.go: the BlockFinalized comment on BridgeResponse, ClaimResponse, SettlementResponse and RootByLERResponse mentions "older versions or finalized block unavailable". It would be clearer to say it is omitted when the syncer cannot determine the block's finality, which is how API_REVISIONS.md words it.
  • revision.go and API_REVISIONS.md say adding an optional field is not a bump, yet revision 1 introduces block_finalized. That is consistent only because revision 1 is the first revision. A one-line note that block_finalized is not itself a reason to bump later would avoid confusion.
  • L1InfoTreeSync.GetInfoByGlobalExitRoot does not fill BlockHash. It is used by the leaf-by-GER handler, which does not report block_finalized, so this is fine for now. If that handler later gains the field, it will silently report unknown.

✅ Looks good

  • The andFinalized logic is correct: false wins, then unknown, then true.
  • The L2GER Legacy mode being reported as unknown is the safe choice.
  • Handlers and mocks are consistent, and test coverage is good: tracked, untracked, hash mismatch, no hash, errors, and the halted processor.
  • omitempty on a *bool keeps the change additive.

🚀 What's New

  • Bridge service responses tied to a block now include an optional block_finalized (true, false or omitted). It is set on /bridges, /bridge-by-deposit-count, /bridges-by-content, /claims, /claims-by-ger, /root-by-ler, /settlements and /injected-l1-info-leaf.
  • Finality comes from the syncer that served the data via a new IsBlockFinalized(blockNum, blockHash):
    • L1InfoTreeSync in multidownloader mode uses the stored IsFinal flag.
    • BridgeSync, ClaimSync, L2GERSync and legacy L1InfoTreeSync use the reorg detector: a stored block whose hash matches and that is no longer tracked counts as finalized.
    • The field is omitted whenever finality is unknown.
  • Bridge, Claim, GlobalExitRootInfo, L1InfoTreeLeaf and tree.Root carry a non-persisted BlockHash, filled by the read queries.
  • GET / and GET /health return api_revision (bridgeservice.CurrentAPIRevision, starting at 1). It is documented in bridgeservice/docs/API_REVISIONS.md and guarded by TestCurrentAPIRevisionHasHistoryEntry.
  • Swagger and docs are updated, with new mocks and tests.

🐛 Bug Fixes

None.

📋 Config Updates

None.

⚠️ Breaking Changes

None. The new fields are optional and additive.

--- | Branch: feat/1915-block-finalized

arnaubennassar
arnaubennassar previously approved these changes Oct 26, 2026
@joanestebanr

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review. Status of each point (pushed in 0734796, 500cd10, c2283f2 and 239d814):

  1. Multidownloader path reporting finality without a hash: fixed (0734796). A zero hash is now unknown in both modes, and the stored header must have the same hash.
  2. Hash read in a separate query: fixed (500cd10). The hash is read in the same SELECT as the rows (LEFT JOIN block) in bridgesync, claimsync, l1infotreesync and l2gersync, including roots and leaves.
  3. Fails open on an unknown subscriber id: fixed (c2283f2). GetTrackedBlockByBlockNumber now returns a new reorgdetector.ErrSubscriberNotFound for an unknown id (an untracked block of a known subscriber still returns db.ErrNotFound), and an unknown id is an error in IsBlockFinalized, never "finalized". The test with the real reorg detector on the simulated backend covers tracked / untracked / unknown id. In bridgesync the driver id and the field now come from one variable.
    While fixing it I found that the order of the checks mattered: reading the stored hash first and asking the tracker afterwards could report an orphaned block as finalized if a whole reorg completed between the two (the detector only drops a block from the tracker after the syncer has deleted it). The shared function asks the tracker first and the DB afterwards; there is a deterministic test of that order.
  4. Duplicated code: fixed (c2283f2). The tracked / not tracked logic lives in one reorgdetector.IsBlockFinalized used by the four legacy syncers; each getBlockHash stays as the callback.
  5. One lookup per row: not done in this PR. It is a sub-millisecond primary key read plus an in-memory lookup per row, and most items of a page sit in different blocks, so a per-request cache would save little; a batched API would change five interfaces. Happy to do it separately if it shows up in profiles.

Optional ones: the BlockFinalized comment now says it is omitted when the syncer can't determine it (239d814). About the API_REVISIONS.md note: an instance without api_revision predates the contract and has no block_finalized, and revision 1 includes it, so no extra note was needed; instead I fixed the bump rule there, which wrongly exempted optional fields (the issue and the tracker's history bump for them), and the revision 1 entry now says so. GetInfoByGlobalExitRoot not filling BlockHash is noted in the PR description.

joanestebanr and others added 12 commits October 26, 2026 16:53
Report whether the block of each item is finalized on its network in
/bridges, /claims, /injected-l1-info-leaf, /root-by-ler and /settlements
(plus the bridge/claim lookups sharing the same response types). The field
is a nullable bool omitted when the finalized block cannot be determined,
keeping compatibility with previous versions.

Fixes #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add L1InfoTreeSync.IsBlockFinalized, backed by the multidownloader's stored
IsFinal flag (which is only set once the block hash was validated), and use it
in the bridge service for the L1 blocks of /settlements and
/injected-l1-info-leaf instead of comparing block numbers against the
finalized block. Legacy and read-only syncers report it as unknown, so
block_finalized is omitted.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… syncer

Add BridgeSync.IsBlockFinalized(num, hash). The legacy syncer has no finality
flag, but the reorg detector only stops tracking a block once its hash matched
the canonical chain below the finalized block, so a processed block with the
given hash that is no longer tracked is finalized. The hash read together with
the item is passed so a block replaced after the read is reported as unknown.

Bridge and tree.Root now carry the stored BlockHash (not persisted, filled by
the read queries). The bridge service uses the syncer finality for /bridges,
/bridge-by-deposit-count, /bridges-by-content and /root-by-ler, and drops the
block number comparison against the finalized block. Claims and the L2 side of
/injected-l1-info-leaf omit block_finalized until their syncers report it.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add ClaimSync.IsBlockFinalized(num, hash), with the same rule as the bridge
syncer: a processed block with the given hash that the reorg detector no longer
tracks is finalized, since it only stops tracking it after matching its hash
against the canonical chain below the finalized block.

Claim now carries the stored BlockHash (not persisted, filled by the paged and
by-GER queries). The bridge service uses the claim syncer of the network for
/claims and /claims-by-ger.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e syncer

Add L2GERSync.IsBlockFinalized(num, hash), with the same rule as the bridge and
claim syncers (a processed block with the given hash that the reorg detector no
longer tracks is finalized). It reports unknown for chains synced in Legacy mode,
where the stored block number is the polling head and not the real injection block.

GlobalExitRootInfo now carries the stored BlockHash (not persisted, filled by
GetFirstGERAfterL1InfoTreeIndex). For an L2 lookup, /injected-l1-info-leaf reports
block_finalized only if both the L1 leaf block and the L2 injection block are
finalized, false as soon as one is known not to be, and omits it otherwise.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Apply the same pattern as the other legacy syncers to L1InfoTreeSync: a processed
block with the given hash that the reorg detector no longer tracks is finalized.
IsBlockFinalized now takes the block hash read together with the item: the legacy
path requires it, the multidownloader path (IsFinal flag) checks it when given.

L1InfoTreeLeaf carries the stored BlockHash (not persisted nor serialized, filled
by L1InfoTreeSync.GetInfoByIndex) and the settlements use their joined block hash,
so /injected-l1-info-leaf and /settlements report block_finalized in both modes.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add bridgeservice.CurrentAPIRevision (revision.go, starting at 1, with the bump
rule and revision history, like the bridge tracker's) and return it as
api_revision in GET / and GET /health, in every sync_status case. It is purely
informational and independent of the bridge tracker's revision.

Update the swagger annotations, the health check section of docs/bridge_service.md
and add tests: handler response (every sync_status), client round trip, the
revision history staying in sync with the constant, and the finder's health
checker ignoring the response body (with and without the new field).

Closes #1920

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add bridgeservice/docs/API_REVISIONS.md with how to read api_revision, when to bump
it and the changes of every revision. TestCurrentAPIRevisionHasHistoryEntry now also
checks that the latest entry of that file (and of revision.go) is the current
revision, so the bump can't be forgotten.

Refs #1920

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… finality

The multidownloader stores one block per number, so without the hash of the block the caller read, IsFinal could belong to the block that replaced it after a reorg. A zero hash (rows written before the block table had a hash, or a missing hash) now reports unknown in both modes, so block_finalized is omitted.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The block_finalized flag is computed from the block hash read together
with each item (bridge, claim, L1 info tree leaf, injected GER, exit tree
root). Until now that hash was filled by a second query on the block
table after reading the rows. A reorg processed between both reads, with
the replacement block already inserted, made an orphaned row get the
hash of the block that replaced its own, so IsBlockFinalized validated
the wrong block.

Read the hash with a LEFT JOIN on the block table in the same SELECT
(one statement, one snapshot) in GetBridgesPaged, GetBridgeByDepositCount
and GetBridgesByContent (bridge and bridge_archive), BridgeSync.GetRootByLER,
GetClaimsPaged and GetClaimsByGER, L1InfoTreeSync.GetInfoByIndex and
GetFirstGERAfterL1InfoTreeIndex. Rows are scanned into unexported read
models with a block_hash column and converted to the public types, which
keep BlockHash out of meddler so inserts are unchanged. The read models
have the same fields as the public types, so the conversion only
compiles while both match, and a test checks their meddler columns.

Remove the fillBlockHashes/FillBlockHashes helpers, now unused.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…losed on unknown subscribers

IsBlockFinalized read the syncer's stored hash first and asked the reorg
detector afterwards. On a reorg the detector notifies the syncer (which
deletes the orphaned blocks) and only then stops tracking them, so a full
reorg completing between the two reads made an orphaned block look
finalized. Add reorgdetector.IsBlockFinalized, shared by BridgeSync,
ClaimSync, L2GERSync and L1InfoTreeSync (legacy), which asks the tracker
first and compares the stored hash afterwards.

GetTrackedBlockByBlockNumber returned db.ErrNotFound both for an untracked
block and for an unknown subscriber id, so a wrong id reported every block
as finalized. It now returns the new ErrSubscriberNotFound for the latter,
and IsBlockFinalized turns it into an error.

Refs #1915

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ision bump rule

Reword the BlockFinalized comment on the response types ("omitted when the syncer that
served the item cannot determine it"). In API_REVISIONS.md, drop the exemption for
optional fields from the bump rule (it contradicted the rule of the issue and the bridge
tracker precedent, where adding an optional field bumped the revision), and state in the
revision 1 entry that instances without api_revision predate the contract and have none
of its fields, block_finalized included.

Refs #1915
Refs #1920

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@joanestebanr
joanestebanr force-pushed the feat/1915-block-finalized branch from 239d814 to 44e0740 Compare October 26, 2026 15:53
@joanestebanr
joanestebanr enabled auto-merge (squash) October 26, 2026 16:26
@joanestebanr
joanestebanr merged commit c952c0e into develop Oct 26, 2026
33 checks passed
@joanestebanr
joanestebanr deleted the feat/1915-block-finalized branch October 26, 2026 17:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add "api_revision" to aggkit bridge service feat: bridge service: add field block_finalized [true|false] into several end-points

2 participants