Summary
Two issues in Registry, one correctness and one performance:
- Overflow: account/contract call-counter updates compute
historical + delta with a plain + (no checked_add). A wrapped counter corrupts ranking and activity accounting.
- 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
Overflow — call_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.
Summary
Two issues in
Registry, one correctness and one performance:historical + deltawith a plain+(nochecked_add). A wrapped counter corrupts ranking and activity accounting.get_rank_by_account_key/get_rank_by_contract_idare O(n) linear scans over the rank→key map (there's aRank → Keyindex but no reverseKey → Rankindex). Combined with the O(n log n) full re-rank on everyapply_changes, this scales poorly as the registry grows.Affected file(s)
src/inscriptive/registry/registry.rsLocation
1. Call-counter overflow
src/inscriptive/registry/registry.rs— insideapply_changes:(The
as u64cast on*call_counter_deltaalso warrants a check — ifcall_counter_deltais 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:in_memory_account_ranks/in_memory_contract_ranksareHashMap<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) andget_rank_by_account_keyare both noted "NOTE: Used by the Engine," so these scans happen on the hot path.Root cause / analysis
Overflow —
call_counter_deltais accumulated ephemerally (one increment per call) and applied in bulk; over a long-enough lifetime a counter nearu64::MAXplus a delta wraps. Same class of bug as the balance-increase overflow incoin_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 inrank_accounts/rank_contracts(registry.rs:420-504) runs on every commit.Impact