Skip to content

[registry] Call-counter update can overflow; rank lookups by key are O(n) #18

Description

@GideonBature

Summary

Two issues in Registry, one correctness and one performance:

  1. Overflow: account/contract call-counter updates compute historical + delta with a plain + (no checked_add). A wrapped counter corrupts ranking and activity accounting.
  2. Performance: get_rank_by_account_key / get_rank_by_contract_id are O(n) linear scans over the rank→key map (there's a Rank → Key index but no reverse Key → Rank index). Combined with the O(n log n) full re-rank on every apply_changes, this scales poorly as the registry grows.

Affected file(s)

  • src/inscriptive/registry/registry.rs

Location

1. Call-counter overflow

src/inscriptive/registry/registry.rs — inside apply_changes:

  • Account counters, ~line 1320:
    let new_call_counter = historical_call_counter + *call_counter_delta as u64;   // <-- plain add
  • Contract counters, ~line 1363:
    let new_call_counter = historical_call_counter + *call_counter_delta as u64;   // <-- plain add

(The as u64 cast on *call_counter_delta also warrants a check — if call_counter_delta is ever negative as a signed type, the cast would wrap; confirm the delta type and guard accordingly.)

2. O(n) rank-by-key lookups

src/inscriptive/registry/registry.rs:686-701:

pub fn get_rank_by_account_key(&self, account_key: [u8; 32]) -> Option<Rank> {
    self.in_memory_account_ranks
        .iter()
        .find(|(_, key)| *key == &account_key)
        .map(|(rank, _)| *rank)
}

pub fn get_rank_by_contract_id(&self, contract_id: [u8; 32]) -> Option<Rank> {
    self.in_memory_contract_ranks
        .iter()
        .find(|(_, key)| *key == &contract_id)
        .map(|(rank, _)| *rank)
}

in_memory_account_ranks / in_memory_contract_ranks are HashMap<Rank, AccountKey> (registry.rs:79-80) — keyed by rank, so looking up the rank for a given key requires scanning all entries.

get_account_info_by_account_key (registry.rs:657) and get_rank_by_account_key are both noted "NOTE: Used by the Engine," so these scans happen on the hot path.

Root cause / analysis

Overflowcall_counter_delta is accumulated ephemerally (one increment per call) and applied in bulk; over a long-enough lifetime a counter near u64::MAX plus a delta wraps. Same class of bug as the balance-increase overflow in coin_manager.

Ranking — the README's stated design ("rankings are cached in-memory for fast access") is only half-true: the cache is rank→key, optimized for get_account_by_rank, not the reverse. The reverse direction falls back to a linear scan. The full re-sort in rank_accounts/rank_contracts (registry.rs:420-504) runs on every commit.

Impact

  • Overflow: silent corruption of call counters → wrong ranking (which feeds account referencing / DA compression per the README) and wrong last-activity accounting.
  • Performance: registry-wide O(n) lookups on the engine hot path; O(n log n) re-sort per batch. At scale this dominates commit cost.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions