Improving connection management, add nip05 route calculations

This commit is contained in:
Jonathan Staab 2023-02-10 10:04:38 -06:00
parent 5f1f9f9b69
commit 14e5be129d
5 changed files with 98 additions and 67 deletions

View File

@ -73,12 +73,15 @@ If you like Coracle and want to support its development, you can donate sats via
# Current
- [ ] Implement gossip model https://bountsr.org/code/2023/02/03/gossip-model.html
- [ ] Add nip 05 to calculation
- [ ] Add connection failures to calculation
- [_] Add nip 05 to calculation
- [ ] Make feeds page customizable. This could potentially use the "lists" NIP
- [ ] Show notification at top of feeds: "Showing notes from 3 relays". Click to customize.
- [ ] Click through on relays page to view a feed for only that relay.
- [ ] Custom views: slider between fast/complete with a warning at either extreme
- [ ] Deterministically calculate color for relays, show it on notes. User popper?
- [ ] Likes list
- [ ] Fix anon/new user experience
- [ ] Stream likes rather than load, they're probably what is slowing things down. Figure out how to multiplex them, or store them in the database by count
# Changelog

View File

@ -1,25 +1,23 @@
import type {Relay} from 'nostr-tools'
import {relayInit} from 'nostr-tools'
import {uniqBy, reject, prop, find, whereEq, is} from 'ramda'
import {uniqBy, prop, find, is} from 'ramda'
import {ensurePlural} from 'hurdak/lib/hurdak'
import {isRelay} from 'src/util/nostr'
import {sleep} from 'src/util/misc'
import database from 'src/agent/database'
let connections = []
const connections = []
class Connection {
promise: Promise<void>
nostr: Relay
status: string
url: string
stats: Record<string, number>
lastRequest: number
lastConnectionAttempt: number
constructor(url) {
this.promise = null
this.nostr = this.init(url)
this.nostr = relayInit(url)
this.status = 'new'
this.url = url
this.stats = {
count: 0,
timer: 0,
@ -29,21 +27,12 @@ class Connection {
connections.push(this)
}
init(url) {
const nostr = relayInit(url)
nostr.on('disconnect', () => {
connections = reject(whereEq({url}), connections)
})
return nostr
}
async connect() {
const shouldConnect = (
this.status === 'new'
|| (
this.status === 'error'
&& Date.now() - this.lastRequest > 30_000
&& Date.now() - this.lastConnectionAttempt > 60_000
)
)
@ -61,7 +50,7 @@ class Connection {
}
}
this.lastRequest = Date.now()
this.lastConnectionAttempt = Date.now()
return this
}
@ -78,7 +67,7 @@ class Connection {
const getConnections = () => connections
const findConnection = url => find(whereEq({url}), connections)
const findConnection = url => find(c => c.nostr.url === url, connections)
const connect = async url => {
const conn = findConnection(url) || new Connection(url)
@ -148,19 +137,19 @@ const subscribe = async (relays, filters, {onEvent, onEose}: Record<string, (e:
if (!seen.has(e.id)) {
seen.add(e.id)
onEvent(Object.assign(e, {seen_on: conn.url}))
onEvent(Object.assign(e, {seen_on: conn.nostr.url}))
}
})
}
if (onEose) {
sub.on('eose', () => onEose(conn.url))
sub.on('eose', () => onEose(conn.nostr.url))
}
conn.stats.activeCount += 1
if (conn.stats.activeCount > 10) {
console.warn(`Relay ${conn.url} has >10 active subscriptions`)
console.warn(`Relay ${conn.nostr.url} has >10 active subscriptions`)
}
return Object.assign(sub, {conn})

View File

@ -1,4 +1,4 @@
import {pick, isEmpty} from 'ramda'
import {pick, identity, isEmpty} from 'ramda'
import {nip05} from 'nostr-tools'
import {noop, createMap, ensurePlural, switcherFn} from 'hurdak/lib/hurdak'
import {now, timedelta, shuffle, hash} from 'src/util/misc'
@ -31,8 +31,10 @@ const processProfileEvents = async events => {
const content = JSON.parse(e.content)
// Fire off a nip05 verification
if (content.nip05) {
if (content.nip05 && e.created_at > (person.nip05_updated_at || 0)) {
verifyNip05(e.pubkey, content.nip05)
content.nip05_updated_at = e.created_at
}
return content
@ -146,41 +148,45 @@ const processMessages = async events => {
}
}
// Routes
const getWeight = type => {
if (type === 'kind:10001') return 1
if (type === 'kind:3') return 0.8
if (type === 'kind:2') return 0.5
if (type === 'seen') return 0.2
if (type === 'tag') return 0.1
}
const calculateRoute = (pubkey, url, type, mode, created_at) => {
if (!isRelay(url)) {
return
}
const id = hash([pubkey, url, mode].join('')).toString()
const score = getWeight(type) * (1 - (now() - created_at) / timedelta(30, 'days'))
const route = database.routes.get(id) || {id, pubkey, url, mode, score: 0, count: 0}
const newTotalScore = route.score * route.count + score
const newCount = route.count + 1
if (score > 0) {
return {...route, count: newCount, score: newTotalScore / newCount}
}
}
const processRoutes = async events => {
const updates = []
// Sample events so we're not burning too many resources
events = ensurePlural(shuffle(events)).slice(0, 10)
const updates = {}
const getWeight = type => {
if (type === 'kind:10001') return 1
if (type === 'kind:3') return 0.8
if (type === 'kind:2') return 0.5
if (type === 'seen') return 0.2
if (type === 'tag') return 0.1
}
const putRoute = (pubkey, url, type, mode, created_at) => {
if (!isRelay(url)) {
return
}
const id = hash([pubkey, url, mode].join('')).toString()
const score = getWeight(type) * (1 - (now() - created_at) / timedelta(30, 'days'))
const route = database.routes.get(id) || {id, pubkey, url, mode, score: 0, count: 0}
const newTotalScore = route.score * route.count + score
const newCount = route.count + 1
if (score > 0) {
updates[id] = {...route, count: newCount, score: newTotalScore / newCount}
}
}
for (const e of events) {
for (const e of ensurePlural(shuffle(events)).slice(0, 10)) {
switcherFn(e.kind, {
2: () => {
putRoute(e.pubkey, e.content, 'kind:2', 'read', e.created_at)
putRoute(e.pubkey, e.content, 'kind:2', 'write', e.created_at)
updates.push(
calculateRoute(e.pubkey, e.content, 'kind:2', 'read', e.created_at)
)
updates.push(
calculateRoute(e.pubkey, e.content, 'kind:2', 'write', e.created_at)
)
},
3: () => {
tryJson(() => {
@ -189,11 +195,15 @@ const processRoutes = async events => {
const {write, read} = conditions as Record<string, boolean|string>
if (![false, '!'].includes(write)) {
putRoute(e.pubkey, url, 'kind:3', 'write', e.created_at)
updates.push(
calculateRoute(e.pubkey, url, 'kind:3', 'write', e.created_at)
)
}
if (![false, '!'].includes(read)) {
putRoute(e.pubkey, url, 'kind:3', 'read', e.created_at)
updates.push(
calculateRoute(e.pubkey, url, 'kind:3', 'read', e.created_at)
)
}
})
})
@ -202,11 +212,15 @@ const processRoutes = async events => {
e.tags
.forEach(([url, read, write]) => {
if (![false, '!'].includes(write)) {
putRoute(e.pubkey, url, 'kind:100001', 'write', e.created_at)
updates.push(
calculateRoute(e.pubkey, url, 'kind:100001', 'write', e.created_at)
)
}
if (![false, '!'].includes(read)) {
putRoute(e.pubkey, url, 'kind:100001', 'read', e.created_at)
updates.push(
calculateRoute(e.pubkey, url, 'kind:100001', 'read', e.created_at)
)
}
})
},
@ -216,13 +230,15 @@ const processRoutes = async events => {
// Add tag hints
events.forEach(e => {
Tags.wrap(e.tags).type("p").all().forEach(([_, pubkey, url]) => {
putRoute(pubkey, url, 'tag', 'write', e.created_at)
updates.push(
calculateRoute(pubkey, url, 'tag', 'write', e.created_at)
)
})
})
}
if (!isEmpty(updates)) {
await database.routes.bulkPut(updates)
await database.routes.bulkPut(createMap('id', updates.filter(identity)))
}
}
@ -244,6 +260,16 @@ const verifyNip05 = (pubkey, as) =>
const person = database.getPersonWithFallback(pubkey)
database.people.patch({...person, verified_as: as})
if (result.relays?.length > 0) {
console.log('===== NIP05 VERIFICATION RELAYS', result.relays)
// database.routes.bulkPut(
// createMap('id', result.relays.flatMap(url =>[
// calculateRoute(pubkey, url, 'nip05', 'write', now()),
// calculateRoute(pubkey, url, 'nip05', 'read', now()),
// ]))
// )
}
}
}, noop)

View File

@ -25,8 +25,8 @@
class="absolute inset-0 mt-20 sm:mt-28 modal-content"
transition:fly={{y: 1000, opacity: 1}}
style={nested && `padding-top: 1rem`}>
<dialog open class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto">
<div class="bg-dark border-t border-solid border-medium h-full w-full overflow-auto">
<slot />
</dialog>
</div>
</div>
</div>

View File

@ -9,7 +9,7 @@
import {extractUrls} from "src/util/html"
import Preview from 'src/partials/Preview.svelte'
import Anchor from 'src/partials/Anchor.svelte'
import {settings, modal, renderNote} from "src/app"
import {toast, settings, modal, renderNote} from "src/app"
import {formatTimestamp} from 'src/util/misc'
import Badge from "src/partials/Badge.svelte"
import Compose from "src/partials/Compose.svelte"
@ -106,12 +106,25 @@
replyMentions = getDefaultReplyMentions()
}
const sendReply = () => {
const sendReply = async () => {
let {content, mentions, topics} = reply.parse()
if (content) {
mentions = uniq(mentions.concat(replyMentions))
cmd.createReply(getEventRelays(note), note, content, mentions, topics)
const relays = getEventRelays(note)
const event = await cmd.createReply(relays, note, content, mentions, topics)
toast.show("info", {
text: `Your note has been created!`,
link: {
text: 'View',
href: "/" + nip19.neventEncode({
id: event.id,
relays: pluck('url', relays.slice(0, 5)),
}),
},
})
resetReply()
}