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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions