Files
buzz/mobile/lib/shared/profile/profile_event_parser.dart
Tom Brow 41c5ace937 perf(mobile): move profile sig checks off main thread (#7648)
The mobile app loads user profiles to show names, avatars, and agent
ownership. Some profiles include a signature that proves who owns the
agent. Checking these signatures for a batch of profiles can block
interaction because the checks run on the same thread that draws the
screen and handles input.

This change moves profile batch parsing and signature checks to a
background worker. It processes one batch at a time and combines queued
profile requests. It keeps the same signature checks, preserves newer
profiles that arrive while a batch runs, and discards results after a
community change. Individual live profile updates still use synchronous
verification.

Tests cover newer live updates, community changes, overlapping requests,
stale malformed events, and UI timer responsiveness through the
production parser. The responsiveness test fails if parsing is moved
back to the UI isolate. A regression also exercises a confirmed profile
save while an older batch is paused, including preservation of verified
ownership. All 38 focused tests, all 2,132 mobile tests, and the
analyzer pass.

With 500 valid owner signatures, an AOT test reduced the main-isolate
timer delay from about 3.4 seconds to less than a millisecond. Total
verification time stayed similar. This measures verification work, not
overall screen speed.

GitHub CI passes. The full local test run failed on two timing-sensitive
ACP tests in unchanged Rust code; both passed isolated retries. All
other local checks passed, including the downstream tests and builds run
after that failure.

---------

Signed-off-by: Tom Brow <tomb@block.xyz>
2026-09-15 20:00:37 +00:00

54 lines
1.7 KiB
Dart

import 'dart:isolate';
import '../crypto/nip_oa.dart';
import '../relay/nostr_models.dart';
import 'user_profile.dart';
/// A verified profile together with its NIP-01 replacement order.
typedef ParsedProfileEvent = ({
UserProfile profile,
int createdAt,
String eventId,
});
/// Parses and verifies profile history without blocking the UI isolate.
///
/// Capture only the event batch here, never a provider or its surrounding
/// context. NIP-OA verification performs synchronous elliptic-curve work.
Future<List<ParsedProfileEvent>> parseProfileEventBatch(
List<NostrEvent> events,
) => Isolate.run(() => _parseBatch(events));
List<ParsedProfileEvent> _parseBatch(List<NostrEvent> events) {
final latest = <String, NostrEvent>{};
for (final event in events) {
if (event.kind != 0) continue;
final pubkey = event.pubkey.toLowerCase();
final current = latest[pubkey];
if (current == null ||
event.createdAt > current.createdAt ||
(event.createdAt == current.createdAt &&
event.id.compareTo(current.id) < 0)) {
latest[pubkey] = event;
}
}
return [for (final event in latest.values) parseProfileEvent(event)];
}
/// Parses a single live profile, including its verified owner attribution.
ParsedProfileEvent parseProfileEvent(NostrEvent event) {
final data = ProfileData.fromEvent(event);
return (
profile: UserProfile(
pubkey: data.pubkey.toLowerCase(),
displayName: data.displayName,
avatarUrl: data.avatarUrl,
about: data.about,
nip05Handle: data.nip05,
ownerPubkey: verifiedOaOwnerPubkey(event.tags, event.pubkey),
),
createdAt: event.createdAt,
eventId: event.id,
);
}