nostrdb: add profiles to nostrdb

This adds profiles to nostrdb

- Remove in-memory Profiles caches, nostrdb is as fast as an in-memory cache
- Remove ProfileDatabase and just use nostrdb directly

Changelog-Changed: Use nostrdb for profiles
This commit is contained in:
William Casarin
2023-08-28 07:52:59 -07:00
parent 8586eed635
commit bb4fd75576
42 changed files with 362 additions and 705 deletions

View File

@@ -34,6 +34,7 @@ struct DamusState {
let user_search_cache: UserSearchCache
let music: MusicController?
let video: VideoController
let ndb: Ndb
@discardableResult
func add_zap(zap: Zapping) -> Bool {
@@ -67,12 +68,12 @@ struct DamusState {
let kp = Keypair(pubkey: empty_pub, privkey: nil)
return DamusState.init(
pool: RelayPool(),
pool: RelayPool(ndb: .empty),
keypair: Keypair(pubkey: empty_pub, privkey: empty_sec),
likes: EventCounter(our_pubkey: empty_pub),
boosts: EventCounter(our_pubkey: empty_pub),
contacts: Contacts(our_pubkey: empty_pub),
profiles: Profiles(user_search_cache: user_search_cache),
profiles: Profiles(user_search_cache: user_search_cache, ndb: .empty),
dms: DirectMessagesModel(our_pubkey: empty_pub),
previews: PreviewCache(),
zaps: Zaps(our_pubkey: empty_pub),
@@ -81,9 +82,9 @@ struct DamusState {
relay_filters: RelayFilters(our_pubkey: empty_pub),
relay_model_cache: RelayModelCache(),
drafts: Drafts(),
events: EventCache(),
events: EventCache(ndb: .empty),
bookmarks: BookmarksManager(pubkey: empty_pub),
postbox: PostBox(pool: RelayPool()),
postbox: PostBox(pool: RelayPool(ndb: .empty)),
bootstrap_relays: [],
replies: ReplyCounter(our_pubkey: empty_pub),
muted_threads: MutedThreadsManager(keypair: kp),
@@ -91,7 +92,8 @@ struct DamusState {
nav: NavigationCoordinator(),
user_search_cache: user_search_cache,
music: nil,
video: VideoController()
video: VideoController(),
ndb: .empty
)
}
}

View File

@@ -77,10 +77,7 @@ class FollowersModel: ObservableObject {
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")

View File

@@ -54,22 +54,6 @@ class FollowingModel {
}
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
switch ev {
case .ws_event:
break
case .nostr_event(let nev):
switch nev {
case .ok:
break
case .event(_, let ev):
if ev.kind == 0 {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
case .eose:
break
}
}
// don't need to do anything here really
}
}

View File

@@ -149,6 +149,7 @@ class HomeModel {
}
}
@MainActor
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
if has_sub_id_event(sub_id: sub_id, ev_id: ev.id) {
return
@@ -169,7 +170,8 @@ class HomeModel {
case .contacts:
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
case .metadata:
handle_metadata_event(ev)
// profile metadata processing is handled by nostrdb
break
case .list:
handle_list_event(ev)
case .boost:
@@ -195,6 +197,7 @@ class HomeModel {
}
}
@MainActor
func handle_status_event(_ ev: NostrEvent) {
guard let st = UserStatus(ev: ev) else {
return
@@ -248,7 +251,8 @@ class HomeModel {
nwc_success(state: self.damus_state, resp: resp)
}
}
@MainActor
func handle_zap_event(_ ev: NostrEvent) {
process_zap_event(damus_state: damus_state, ev: ev) { zapres in
guard case .done(let zap) = zapres,
@@ -373,7 +377,7 @@ class HomeModel {
}
}
@MainActor
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
switch conn_event {
case .ws_event(let ev):
@@ -582,10 +586,6 @@ class HomeModel {
damus_state.contacts.set_mutelist(ev)
}
func handle_metadata_event(_ ev: NostrEvent) {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
func get_last_event_of_kind(relay_id: String, kind: UInt32) -> NostrEvent? {
guard let m = last_event_of_kind[relay_id] else {
last_event_of_kind[relay_id] = [:]
@@ -791,45 +791,6 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
}
*/
func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) {
var old_nip05: String? = nil
let mprof = profiles.lookup_with_timestamp(id: ev.pubkey)
if let mprof {
old_nip05 = mprof.profile.nip05
if mprof.event.created_at > ev.created_at {
// skip if we already have an newer profile
return
}
}
if old_nip05 != profile.nip05 {
// if it's been validated before, invalidate it now
profiles.invalidate_nip05(ev.pubkey)
}
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at, event: ev)
profiles.add(id: ev.pubkey, profile: tprof)
// load pfps asap
var changed = false
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
if URL(string: picture) != nil {
changed = true
}
let banner = tprof.profile.banner ?? ""
if URL(string: banner) != nil {
changed = true
}
if changed {
notify(.profile_updated(pubkey: ev.pubkey, profile: profile))
}
}
// TODO: remove this, let nostrdb handle all validation
func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
let validated = events.is_event_valid(ev.id)
@@ -856,24 +817,6 @@ func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping (
}
}
func process_metadata_event(events: EventCache, our_pubkey: Pubkey, profiles: Profiles, ev: NostrEvent, completion: ((Profile?) -> Void)? = nil) {
guard_valid_event(events: events, ev: ev) {
DispatchQueue.global(qos: .background).async {
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
completion?(nil)
return
}
profile.cache_lnurl()
DispatchQueue.main.async {
process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
completion?(profile)
}
}
}
}
func robohash(_ pk: Pubkey) -> String {
return "https://robohash.org/" + pk.hex()
}
@@ -1394,6 +1337,7 @@ func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? {
return pk
}
@MainActor
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
// These are zap notifications
guard let ptag = get_zap_target_pubkey(ev: ev, events: damus_state.events) else {
@@ -1417,17 +1361,13 @@ func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @esc
return
}
guard let profile = damus_state.profiles.lookup(id: ptag) else {
completion(.failed)
return
}
guard let lnurl = profile.lnurl else {
guard let record = damus_state.profiles.lookup_with_timestamp(ptag),
let lnurl = record.lnurl else {
completion(.failed)
return
}
Task {
Task { [lnurl] in
guard let zapper = await fetch_zapper_from_lnurl(lnurls: damus_state.lnurls, pubkey: ptag, lnurl: lnurl) else {
completion(.failed)
return

View File

@@ -102,8 +102,6 @@ class ProfileModel: ObservableObject, Equatable {
}
} else if ev.known_kind == .contacts {
handle_profile_contact_event(ev)
} else if ev.known_kind == .metadata {
process_metadata_event(events: damus.events, our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
}
seen_event.insert(ev.id)
}

View File

@@ -139,21 +139,13 @@ func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad
authors: authors)
damus_state.pool.subscribe_to(sub_id: profiles_subid, filters: [filter], to: [relay_id]) { sub_id, conn_ev in
let (sid, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: conn_ev) { sub_id, ev in
guard sub_id == profiles_subid else {
return
}
if ev.known_kind == .metadata {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
}
guard done && sid == profiles_subid else {
guard case .nostr_event(let ev) = conn_ev,
case .eose = ev,
sub_id == profiles_subid
else {
return
}
print("done loading \(authors.count) profiles from \(relay_id)")
damus_state.pool.unsubscribe(sub_id: profiles_subid, to: [relay_id])
}

View File

@@ -97,7 +97,8 @@ class ThreadModel: ObservableObject {
event_map.insert(ev)
objectWillChange.send()
}
@MainActor
func handle_event(relay_id: String, ev: NostrConnectionEvent) {
let (sub_id, done) = handle_subid_event(pool: damus_state.pool, relay_id: relay_id, ev: ev) { sid, ev in
@@ -105,9 +106,7 @@ class ThreadModel: ObservableObject {
return
}
if ev.known_kind == .metadata {
process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
} else if ev.known_kind == .zap {
if ev.known_kind == .zap {
process_zap_event(damus_state: damus_state, ev: ev) { zap in
}

View File

@@ -10,6 +10,9 @@ import Foundation
/// Cache of searchable users by name, display_name, NIP-05 identifier, or own contact list petname.
/// Optimized for fast searches of substrings by using a Trie.
/// Optimal for performing user searches that could be initiated by typing quickly on a keyboard into a text input field.
// TODO: replace with lmdb (the b tree should handle this just fine ?)
// we just need a name to profile index
class UserSearchCache {
private let trie = Trie<Pubkey>()
@@ -19,6 +22,7 @@ class UserSearchCache {
}
/// Computes the differences between an old profile, if it exists, and a new profile, and updates the user search cache accordingly.
@MainActor
func updateProfile(id: Pubkey, profiles: Profiles, oldProfile: Profile?, newProfile: Profile) {
// Remove searchable keys tied to the old profile if they differ from the new profile
// to keep the trie clean without empty nodes while avoiding excessive graph searching.
@@ -38,6 +42,7 @@ class UserSearchCache {
}
/// Adds a profile to the user search cache.
@MainActor
private func addProfile(id: Pubkey, profiles: Profiles, profile: Profile) {
// Searchable by name.
if let name = profile.name {

View File

@@ -37,7 +37,8 @@ class ZapsModel: ObservableObject {
func unsubscribe() {
state.pool.unsubscribe(sub_id: zaps_subid)
}
@MainActor
func handle_event(relay_id: String, conn_ev: NostrConnectionEvent) {
guard case .nostr_event(let resp) = conn_ev else {
return