Skip to content

fix(minibf): fix ordering and 404 edge cases in account addresses - #1221

Draft
slowbackspace wants to merge 2 commits into
mainfrom
fix/minibf-account-addresses
Draft

fix(minibf): fix ordering and 404 edge cases in account addresses#1221
slowbackspace wants to merge 2 commits into
mainfrom
fix/minibf-account-addresses

Conversation

@slowbackspace

@slowbackspace slowbackspace commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Closes #1140.

Summary

Fixes two blockfrost-tests failures in GET /accounts/{stake_address}/addresses: wrong desc ordering and a 404 for pool-only accounts. Also adds a stake address log index (applied from #1225) that serves the endpoint in O(page) time.

Bug 1: order=desc returned the wrong order

  • Cause: the handler scanned blocks newest-first for desc and kept the first hit per address. This ordered reused addresses by their latest appearance; Blockfrost orders by first appearance.
  • Fix: scan ascending in both cases to record first appearances. For desc, reverse the full list and then apply the page window; enforce_max_scan_limit still guards the page window.
  • Proof: the mainnet fixture account stake1u9uz4j... reuses its oldest address. All four mainnet fixture cases now match a fully synced mainnet snapshot (the desc case failed before).

Bug 2: pool-only accounts returned 404

  • Cause: the preprod fixture credential stake_test1uzkdwx64... appears on chain only as reward account and owner of three pools. Dolos creates no AccountState for it, so the existence guard returned 404; Blockfrost returns 200 [] because db-sync registers pool reward accounts and owners.
  • Fix: before returning 404, scan PoolState for a pool that names the credential as reward account or owner. The scan runs only on the would-be-404 path, so the hot path pays nothing.

Stake address log (applied from #1225)

The corrected desc order must scan the account's full archive history before it can reverse the list. Measured on a fully synced mainnet snapshot: 305 seconds and ~274MB transient RSS for an exchange account with 400k+ addresses — also a DoS shape on public nodes.

The log stores each (stake credential, address) pair once, at its first on-chain appearance, ordered by (slot, tx order, output order). Both asc and desc become a single page read (desc is a reverse range read).

  • fjall: new stake-log keyspace with two entry shapes — a membership entry per pair (write-path probe and undo key) and an ordered entry (page read). The writer keeps a batch-local seen-set because a batch cannot read its own pending inserts.
  • memory: implements the same semantics, so ToyDomain endpoint tests exercise the log. redb3 and noop answer None (redb3 is deprecated for index stores).
  • Undo: compute_undo derives appearances from the same stake_appearances_from_block function as the apply path. A pair is removed only when the undone block is its stored first appearance.
  • Rollout: a ready marker gates reads; genesis bootstrap sets it. Existing stores answer None and the endpoint transparently falls back to the archive scan until they resync — resync is the rollout path, no doctor/backfill job.
  • Size: full address bytes are stored so pointer addresses round-trip. Estimated mainnet size: 5–15GB against a 340GB store.

Testing

  • accounts_by_stake_addresses_order_desc asserts desc equals the reverse of asc and that a desc page is a window into the reversed list; the old implementation fails it.
  • New accounts_by_stake_addresses_pool_only_account_returns_empty_list queries the synthetic pool owner credential with no AccountState; the old implementation fails with 404.
  • Backend conformance test in tests/index_roundtrip.rs runs against fjall and memory: marker gating, first-appearance dedup, ordered paging from both ends, offset windows, stake isolation, and undo-of-first-appearance-only.
  • Endpoint equivalence test: two identical synthetic chains, one served from the log and one from the archive-scan fallback, return identical responses for every query shape.
  • Full workspace suite passes; clippy clean; nightly fmt applied.
  • Not yet done: an end-to-end run against a freshly synced network store. Existing local stores predate the log, so they exercise only the fallback.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ff58192f-d78e-44a5-8146-6a60a62e7986

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@michalrus

Copy link
Copy Markdown
Contributor

cargo-deny fixed in:

The /accounts/{stake_address}/addresses endpoint diverged from
Blockfrost in two cases:

- order=desc sorted addresses by their latest on-chain appearance.
  Blockfrost returns the exact reverse of the asc list, which orders
  addresses by first appearance. Reused addresses came out in the
  wrong position.

- Accounts that only appear inside pool registrations (reward account
  or pool owner) returned 404. Blockfrost knows these credentials and
  returns an empty list.

Fixes #1140
The /accounts/{stake_address}/addresses endpoint scans every archive
block that touches the account. With correct first-appearance ordering,
a desc request must scan the account's full history: 305 seconds
measured on mainnet for an exchange account with 400k+ addresses.

The stake address log stores each (stake credential, address) pair
once, at its first on-chain appearance, ordered by slot, transaction
order, and output order. Both orders become one page read.

- fjall: new stake-log keyspace with membership entries (the write
  probe and undo key) and ordered entries (the page read).
- memory: same semantics, so ToyDomain tests exercise the log.
- redb3 and noop answer None; redb3 is deprecated for index stores.
- The apply path emits appearances from index_block. The undo path
  derives them from the same function, so a rollback removes exactly
  what apply inserted, and only when the undone block was the pair's
  first appearance.
- A ready marker gates reads. Genesis bootstrap sets it, so stores
  synced from scratch serve the log. Existing stores answer None and
  the endpoint falls back to the archive scan until a resync.
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.

minibf: fix /accounts/<account>/addresses

2 participants