mirror of
https://github.com/KoalaSat/nostros.git
synced 2024-09-29 06:30:47 +00:00
resilience Exposure
This commit is contained in:
parent
f8b061f3f8
commit
887664b50d
@ -44,6 +44,12 @@ public class Event {
|
||||
public void save(SQLiteDatabase database, String userPubKey, String relayUrl) {
|
||||
if (isValid()) {
|
||||
try {
|
||||
ContentValues relayValues = new ContentValues();
|
||||
relayValues.put("note_id", id);
|
||||
relayValues.put("pubkey", pubkey);
|
||||
relayValues.put("relay_url", relayUrl);
|
||||
database.replace("nostros_notes_relays", null, relayValues);
|
||||
|
||||
if (kind.equals("0")) {
|
||||
saveUserMeta(database);
|
||||
} else if (kind.equals("1") || kind.equals("2")) {
|
||||
@ -180,12 +186,6 @@ public class Event {
|
||||
}
|
||||
|
||||
protected void saveNote(SQLiteDatabase database, String userPubKey, String relayUrl) {
|
||||
ContentValues relayValues = new ContentValues();
|
||||
relayValues.put("note_id", id);
|
||||
relayValues.put("pubkey", pubkey);
|
||||
relayValues.put("relay_url", relayUrl);
|
||||
database.replace("nostros_notes_relays", null, relayValues);
|
||||
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("id", id);
|
||||
values.put("content", content.replace("'", "''"));
|
||||
@ -286,6 +286,7 @@ public class Event {
|
||||
for (int i = 0; i < tags.length(); ++i) {
|
||||
JSONArray tag = tags.getJSONArray(i);
|
||||
String petId = tag.getString(1);
|
||||
String relay = tag.getString(2);
|
||||
String name = "";
|
||||
if (tag.length() >= 4) {
|
||||
name = tag.getString(3);
|
||||
@ -298,6 +299,7 @@ public class Event {
|
||||
values.put("name", name);
|
||||
values.put("contact", true);
|
||||
values.put("blocked", 0);
|
||||
values.put("main_relay", relay);
|
||||
values.put("pet_at", created_at);
|
||||
database.insert("nostros_users", null, values);
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ public class DatabaseModule {
|
||||
" picture TEXT,\n" +
|
||||
" about TEXT,\n" +
|
||||
" main_relay TEXT,\n" +
|
||||
" contact BOOLEAN DEFAULT 0,\n" +
|
||||
" follower BOOLEAN DEFAULT 0\n" +
|
||||
" contact INT DEFAULT 0,\n" +
|
||||
" follower INT DEFAULT 0\n" +
|
||||
" );");
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS nostros_relays(\n" +
|
||||
" url TEXT PRIMARY KEY NOT NULL,\n" +
|
||||
@ -56,11 +56,11 @@ public class DatabaseModule {
|
||||
" sig TEXT NOT NULL,\n" +
|
||||
" tags TEXT NOT NULL,\n" +
|
||||
" conversation_id TEXT NOT NULL,\n" +
|
||||
" read BOOLEAN DEFAULT 0\n" +
|
||||
" read INT DEFAULT 0\n" +
|
||||
" );");
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN user_mentioned BOOLEAN DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN seen BOOLEAN DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN user_mentioned INT DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN seen INT DEFAULT 0;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN lnurl TEXT;");
|
||||
@ -77,7 +77,7 @@ public class DatabaseModule {
|
||||
" pubkey TEXT NOT NULL,\n" +
|
||||
" sig TEXT NOT NULL,\n" +
|
||||
" tags TEXT NOT NULL,\n" +
|
||||
" positive BOOLEAN DEFAULT 1,\n" +
|
||||
" positive INT DEFAULT 1,\n" +
|
||||
" reacted_event_id TEXT,\n" +
|
||||
" reacted_user_id TEXT\n" +
|
||||
" );");
|
||||
@ -98,15 +98,11 @@ public class DatabaseModule {
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN nip05 TEXT;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN valid_nip05 BOOLEAN DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN valid_nip05 INT DEFAULT 0;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_notes ADD COLUMN repost_id TEXT;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN active BOOLEAN DEFAULT 1;");
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN active INT DEFAULT 1;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("CREATE INDEX nostros_notes_repost_id_created_at_index ON nostros_notes(repost_id, pubkey, created_at); ");
|
||||
@ -122,12 +118,9 @@ public class DatabaseModule {
|
||||
|
||||
database.execSQL("CREATE INDEX nostros_users_contact_follower_index ON nostros_users(contact, follower); ");
|
||||
database.execSQL("CREATE INDEX nostros_users_id_name_index ON nostros_users(id, name); ");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
|
||||
database.execSQL("DROP TABLE IF EXISTS nostros_config;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN blocked BOOLEAN DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN blocked INT DEFAULT 0;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("CREATE TABLE IF NOT EXISTS nostros_notes_relays(\n" +
|
||||
@ -140,9 +133,13 @@ public class DatabaseModule {
|
||||
database.execSQL("CREATE INDEX nostros_notes_relays_pubkey_index ON nostros_notes_relays(pubkey);");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN global_feed BOOLEAN DEFAULT 1;");
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN pet_at INT;");
|
||||
database.execSQL("ALTER TABLE nostros_users ADD COLUMN follower_at INT;");
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN global_feed INT DEFAULT 1;");
|
||||
} catch (SQLException e) { }
|
||||
try {
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN resilient INT DEFAULT 0;");
|
||||
database.execSQL("ALTER TABLE nostros_relays ADD COLUMN manual INT DEFAULT 1;");
|
||||
} catch (SQLException e) { }
|
||||
}
|
||||
|
||||
|
@ -1,45 +1 @@
|
||||
export const defaultRelays = [
|
||||
'wss://brb.io',
|
||||
'wss://damus.io',
|
||||
'wss://nostr-pub.wellorder.net',
|
||||
'wss://nostr.swiss-enigma.ch',
|
||||
'wss://nostr.onsats.org',
|
||||
'wss://nostr-pub.semisol.dev',
|
||||
'wss://nostr.openchain.fr',
|
||||
'wss://relay.nostr.info',
|
||||
'wss://nostr.oxtr.dev',
|
||||
'wss://nostr.ono.re',
|
||||
'wss://relay.grunch.dev',
|
||||
'wss://nostr.developer.li',
|
||||
]
|
||||
|
||||
export const REGEX_SOCKET_LINK = /^wss:\/\/.*\..*$/i
|
||||
|
||||
export const relayColors = [
|
||||
'#3016dd',
|
||||
'#43e1ef',
|
||||
'#ef3b50',
|
||||
'#d3690c',
|
||||
'#43e23b',
|
||||
'#3a729e',
|
||||
'#805de2',
|
||||
'#b2ff5b',
|
||||
'#eaa123',
|
||||
'#ba7a3b',
|
||||
'#90c900',
|
||||
'#26e08c',
|
||||
'#090660',
|
||||
'#9edb62',
|
||||
'#db48f2',
|
||||
'#5d14aa',
|
||||
'#f2d859',
|
||||
'#0b8458',
|
||||
'#cdea10',
|
||||
'#6473e0',
|
||||
'#6721a5',
|
||||
'#f76f8c',
|
||||
'#ce2780',
|
||||
'#403ba0',
|
||||
'#a9f41d',
|
||||
'#BBF067',
|
||||
]
|
||||
export const REGEX_SOCKET_LINK = /^(ws|wss):\/\/.*\..*$/i
|
||||
|
@ -3,7 +3,7 @@ import RelayPool from '../lib/nostr/RelayPool/intex'
|
||||
import { AppContext } from './AppContext'
|
||||
import { DeviceEventEmitter } from 'react-native'
|
||||
import debounce from 'lodash.debounce'
|
||||
import { createRelay, getRelays, Relay } from '../Functions/DatabaseFunctions/Relays'
|
||||
import { getRelays, Relay } from '../Functions/DatabaseFunctions/Relays'
|
||||
import { UserContext } from './UserContext'
|
||||
|
||||
export interface RelayPoolContextProps {
|
||||
@ -74,6 +74,7 @@ export const RelayPoolContextProvider = ({
|
||||
DeviceEventEmitter.addListener('WebsocketEvent', debouncedEventIdHandler)
|
||||
DeviceEventEmitter.addListener('WebsocketConfirmation', debouncedConfirmationHandler)
|
||||
const initRelayPool = new RelayPool(privateKey)
|
||||
await initRelayPool.resilientMode(database, publicKey)
|
||||
initRelayPool.connect(publicKey, () => setRelayPoolReady(true))
|
||||
setRelayPool(initRelayPool)
|
||||
loadRelays()
|
||||
@ -140,18 +141,6 @@ export const RelayPoolContextProvider = ({
|
||||
}
|
||||
}, [publicKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (database) {
|
||||
getRelays(database).then((results) => {
|
||||
if (results.length === 0) {
|
||||
createRelay(database, 'wss://brb.io')
|
||||
createRelay(database, 'wss://damus.io')
|
||||
createRelay(database, 'wss://nostr-pub.wellorder.net')
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [database])
|
||||
|
||||
return (
|
||||
<RelayPoolContext.Provider
|
||||
value={{
|
||||
|
50
frontend/Functions/DatabaseFunctions/NotesRelays/index.ts
Normal file
50
frontend/Functions/DatabaseFunctions/NotesRelays/index.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
|
||||
export interface NoteRelay {
|
||||
relay_url: string
|
||||
pubkey: string
|
||||
note_id: number
|
||||
}
|
||||
|
||||
export const getNoteRelaysUsage: (
|
||||
db: QuickSQLiteConnection,
|
||||
) => Promise<Record<string, number>> = async (db) => {
|
||||
const result: Record<string, number> = {}
|
||||
const query = `
|
||||
SELECT relay_url, COUNT(*) as count FROM nostros_notes_relays
|
||||
LEFT JOIN
|
||||
nostros_users ON nostros_users.id = nostros_notes_relays.pubkey
|
||||
WHERE nostros_users.contact > 0
|
||||
GROUP BY relay_url
|
||||
`
|
||||
const resultSet = db.execute(query)
|
||||
if (resultSet?.rows?.length) {
|
||||
for (let index = 0; index < resultSet?.rows?.length; index++) {
|
||||
const row = resultSet?.rows?.item(index)
|
||||
result[row.relay_url] = row.count
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const getNoteRelaysPresence: (
|
||||
db: QuickSQLiteConnection,
|
||||
) => Promise<Record<string, string[]>> = async (db) => {
|
||||
const result: Record<string, string[]> = {}
|
||||
const query = `
|
||||
SELECT relay_url, pubkey FROM nostros_notes_relays
|
||||
LEFT JOIN
|
||||
nostros_users ON nostros_users.id = nostros_notes_relays.pubkey
|
||||
WHERE nostros_users.contact > 0
|
||||
GROUP BY relay_url, pubkey
|
||||
ORDER BY random()
|
||||
`
|
||||
const resultSet = db.execute(query)
|
||||
if (resultSet?.rows?.length) {
|
||||
for (let index = 0; index < resultSet?.rows?.length; index++) {
|
||||
const row = resultSet?.rows?.item(index)
|
||||
result[row.relay_url] = [...(result[row.relay_url] ?? []), row.pubkey]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import { QueryResult, QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { getItems } from '..'
|
||||
import { median } from '../../NativeFunctions'
|
||||
import { getNoteRelaysUsage } from '../NotesRelays'
|
||||
|
||||
export interface Relay {
|
||||
url: string
|
||||
name?: string
|
||||
active?: number
|
||||
global_feed?: number
|
||||
resilient?: number
|
||||
manual?: number
|
||||
}
|
||||
|
||||
export interface RelayInfo {
|
||||
@ -61,3 +65,39 @@ export const createRelay: (db: QuickSQLiteConnection, url: string) => Promise<Qu
|
||||
`
|
||||
return db.execute(query, [url])
|
||||
}
|
||||
|
||||
export const createResilientRelay: (
|
||||
db: QuickSQLiteConnection,
|
||||
url: string,
|
||||
) => Promise<QueryResult> = async (db, url) => {
|
||||
const query = `
|
||||
INSERT OR IGNORE INTO nostros_relays (url, resilient, active) VALUES (?, ?, ?)
|
||||
`
|
||||
return db.execute(query, [url, 1, 0])
|
||||
}
|
||||
|
||||
export const activateResilientRelays: (
|
||||
db: QuickSQLiteConnection,
|
||||
relayUrls: string[],
|
||||
) => Promise<QueryResult> = async (db, relayUrls) => {
|
||||
const userQuery = `UPDATE nostros_relays SET resilient = 1 WHERE url IN ('${relayUrls.join(
|
||||
"', '",
|
||||
)}');`
|
||||
return db.execute(userQuery)
|
||||
}
|
||||
|
||||
export const desactivateResilientRelays: (
|
||||
db: QuickSQLiteConnection,
|
||||
) => Promise<QueryResult> = async (db) => {
|
||||
const userQuery = `UPDATE nostros_relays SET resilient = 0 WHERE resilient = 1;`
|
||||
return db.execute(userQuery)
|
||||
}
|
||||
|
||||
export const getResilientRelays: (db: QuickSQLiteConnection) => Promise<string[]> = async (db) => {
|
||||
const relayUsage = await getNoteRelaysUsage(db)
|
||||
const medianUsage = median(Object.values(relayUsage))
|
||||
const resilientRelays = Object.keys(relayUsage).sort((n1: string, n2: string) => {
|
||||
return Math.abs(relayUsage[n1] - medianUsage) - Math.abs(relayUsage[n2] - medianUsage)
|
||||
})
|
||||
return resilientRelays
|
||||
}
|
||||
|
@ -68,6 +68,25 @@ export const addUser: (pubKey: string, db: QuickSQLiteConnection) => Promise<Que
|
||||
return db.execute(query, [pubKey])
|
||||
}
|
||||
|
||||
export const getMainRelays: (
|
||||
db: QuickSQLiteConnection,
|
||||
) => Promise<Record<string, { count: number }>> = async (db) => {
|
||||
const result: Record<string, { count: number }> = {}
|
||||
const query = `
|
||||
SELECT main_relay, COUNT(*) as count FROM nostros_users
|
||||
WHERE nostros_users.contact > 0
|
||||
GROUP BY main_relay
|
||||
`
|
||||
const resultSet = db.execute(query)
|
||||
if (resultSet?.rows?.length) {
|
||||
for (let index = 0; index < resultSet?.rows?.length; index++) {
|
||||
const row = resultSet?.rows?.item(index)
|
||||
console.log(row)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const getContactsCount: (db: QuickSQLiteConnection) => Promise<number> = async (db) => {
|
||||
const countQuery = 'SELECT COUNT(*) FROM nostros_users WHERE contact = 1'
|
||||
const resultSet = db.execute(countQuery)
|
||||
|
@ -32,6 +32,8 @@ export const dropTables: (db: QuickSQLiteConnection) => Promise<BatchQueryResult
|
||||
const dropQueries: Array<[string, [any[] | any[][]]]> = [
|
||||
['DELETE FROM nostros_users;', [[]]],
|
||||
['DELETE FROM nostros_notes;', [[]]],
|
||||
['DELETE FROM nostros_reactions;', [[]]],
|
||||
['DELETE FROM nostros_notes_relays;', [[]]],
|
||||
['DELETE FROM nostros_direct_messages;', [[]]],
|
||||
]
|
||||
return db.executeBatch(dropQueries)
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { relayColors } from '../../Constants/Relay'
|
||||
|
||||
export const handleInfinityScroll: (event: any) => boolean = (event) => {
|
||||
const mHeight = event.nativeEvent.layoutMeasurement.height
|
||||
const cSize = event.nativeEvent.contentSize.height
|
||||
@ -9,6 +7,35 @@ export const handleInfinityScroll: (event: any) => boolean = (event) => {
|
||||
return false
|
||||
}
|
||||
|
||||
export const relayColors = [
|
||||
'#3016dd',
|
||||
'#43e1ef',
|
||||
'#ef3b50',
|
||||
'#d3690c',
|
||||
'#43e23b',
|
||||
'#3a729e',
|
||||
'#805de2',
|
||||
'#b2ff5b',
|
||||
'#eaa123',
|
||||
'#ba7a3b',
|
||||
'#90c900',
|
||||
'#26e08c',
|
||||
'#090660',
|
||||
'#9edb62',
|
||||
'#db48f2',
|
||||
'#5d14aa',
|
||||
'#f2d859',
|
||||
'#0b8458',
|
||||
'#cdea10',
|
||||
'#6473e0',
|
||||
'#6721a5',
|
||||
'#f76f8c',
|
||||
'#ce2780',
|
||||
'#403ba0',
|
||||
'#a9f41d',
|
||||
'#BBF067',
|
||||
]
|
||||
|
||||
export const relayToColor: (string: string) => string = (string) => {
|
||||
let hash = 0
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
@ -61,3 +88,9 @@ export const validNip21: (string: string | undefined) => boolean = (string) => {
|
||||
|
||||
export const randomInt: (min: number, max: number) => number = (min, max) =>
|
||||
Math.floor(Math.random() * (max - min + 1)) + min
|
||||
|
||||
export const median: (arr: number[]) => number = (arr) => {
|
||||
const mid = Math.floor(arr.length / 2)
|
||||
const nums = [...arr].sort((a, b) => a - b)
|
||||
return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2
|
||||
}
|
||||
|
@ -183,6 +183,14 @@
|
||||
}
|
||||
},
|
||||
"relaysPage": {
|
||||
"resilienceTitle": "Resilience (experimental)",
|
||||
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.",
|
||||
"resilienceCategories": "Resilience exposure",
|
||||
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.",
|
||||
"centralized": "Centralized",
|
||||
"small": "Small",
|
||||
"contacts": "# Contacts",
|
||||
"resilienceMode": "Resilience (experimental)",
|
||||
"relayName": "Addresse",
|
||||
"globalFeed": "Globaler Feed",
|
||||
"active": "Aktiv",
|
||||
|
@ -166,6 +166,14 @@
|
||||
"newMessages": "{{newNotesCount}} new notes. Pull to refresh."
|
||||
},
|
||||
"relaysPage": {
|
||||
"resilienceTitle": "Resilience (experimental)",
|
||||
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.",
|
||||
"resilienceCategories": "Resilience exposure",
|
||||
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.",
|
||||
"centralized": "Centralized",
|
||||
"small": "Small",
|
||||
"contacts": "# Contacts",
|
||||
"resilienceMode": "Resilience (experimental)",
|
||||
"relayName": "Address",
|
||||
"globalFeed": "Global feed",
|
||||
"active": "Active",
|
||||
|
@ -179,6 +179,14 @@
|
||||
}
|
||||
},
|
||||
"relaysPage": {
|
||||
"resilienceTitle": "Resilience (experimental)",
|
||||
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.",
|
||||
"resilienceCategories": "Resilience exposure",
|
||||
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.",
|
||||
"centralized": "Centralizado",
|
||||
"small": "Pequeño",
|
||||
"contacts": "Nº contactos",
|
||||
"resilienceMode": "Resiliencia (experimental)",
|
||||
"relayName": "Dirección",
|
||||
"globalFeed": "Feed global",
|
||||
"active": "Activo",
|
||||
|
@ -178,6 +178,14 @@
|
||||
}
|
||||
},
|
||||
"relaysPage": {
|
||||
"resilienceTitle": "Resilience (experimental)",
|
||||
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.",
|
||||
"resilienceCategories": "Resilience exposure",
|
||||
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.",
|
||||
"centralized": "Centralized",
|
||||
"small": "Small",
|
||||
"contacts": "# Contacts",
|
||||
"resilienceMode": "Resilience (experimental)",
|
||||
"relayName": "Adresse",
|
||||
"globalFeed": "Flux global",
|
||||
"active": "Actif",
|
||||
|
@ -178,6 +178,14 @@
|
||||
}
|
||||
},
|
||||
"relaysPage": {
|
||||
"resilienceTitle": "Resilience (experimental)",
|
||||
"resilienceDescription": "The nostr protocol provides a good amount of tools to build a desentralized network, but unlike other desentralized protocols such as Bitcoin, it's not inherent by defaul on his usage.\n\nTo ahcieve such goal, clients and relays should activally colaborate. Nostros resiliense mode tries to, first, expose these concerns to their users, and secondly, start experimening with new pattern and way to help on network' resilency.\n\nFunctional implementation will soon endup appearing on Nostros by default.",
|
||||
"resilienceCategories": "Resilience exposure",
|
||||
"resilienceCategoriesDescription": "A first approach Nostros is testing is randomly calculate (based on received contats' events) what are the most resilient relays. It tries to avoid both relays with too much events (centralization) and relays with almost no events (battery draining).\n\nThe goal is to generate a list of 5 relays and cover all contacts. But if this is not possible, it will search among other realys, warning to the user about it.",
|
||||
"centralized": "Centralized",
|
||||
"small": "Small",
|
||||
"contacts": "# Contacts",
|
||||
"resilienceMode": "Resilience (experimental)",
|
||||
"relayName": "Address",
|
||||
"globalFeed": "Общая лента",
|
||||
"active": "Active",
|
||||
|
@ -36,7 +36,7 @@ interface ConversationPageProps {
|
||||
export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) => {
|
||||
const theme = useTheme()
|
||||
const scrollViewRef = useRef<ScrollView>()
|
||||
const { database } = useContext(AppContext)
|
||||
const { database, setRefreshBottomBarAt } = useContext(AppContext)
|
||||
const { relayPool, lastEventId } = useContext(RelayPoolContext)
|
||||
const { publicKey, privateKey, name } = useContext(UserContext)
|
||||
const otherPubKey = useMemo(() => route.params.pubKey, [])
|
||||
@ -64,6 +64,7 @@ export const ConversationPage: React.FC<ConversationPageProps> = ({ route }) =>
|
||||
if (database && publicKey) {
|
||||
const conversationId = route.params?.conversationId
|
||||
updateConversationRead(conversationId, database)
|
||||
setRefreshBottomBarAt(getUnixTime(new Date()))
|
||||
getUser(otherPubKey, database).then((user) => {
|
||||
if (user) setOtherUser(user)
|
||||
})
|
||||
|
@ -4,7 +4,7 @@ import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RelayPoolContext } from '../../Contexts/RelayPoolContext'
|
||||
import { Relay } from '../../Functions/DatabaseFunctions/Relays'
|
||||
import { defaultRelays, REGEX_SOCKET_LINK } from '../../Constants/Relay'
|
||||
import { REGEX_SOCKET_LINK } from '../../Constants/Relay'
|
||||
import {
|
||||
List,
|
||||
Switch,
|
||||
@ -22,6 +22,21 @@ import { relayToColor } from '../../Functions/NativeFunctions'
|
||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
|
||||
export const defaultRelays = [
|
||||
'wss://brb.io',
|
||||
'wss://damus.io',
|
||||
'wss://nostr-pub.wellorder.net',
|
||||
'wss://nostr.swiss-enigma.ch',
|
||||
'wss://nostr.onsats.org',
|
||||
'wss://nostr-pub.semisol.dev',
|
||||
'wss://nostr.openchain.fr',
|
||||
'wss://relay.nostr.info',
|
||||
'wss://nostr.oxtr.dev',
|
||||
'wss://nostr.ono.re',
|
||||
'wss://relay.grunch.dev',
|
||||
'wss://nostr.developer.li',
|
||||
]
|
||||
|
||||
export const RelaysPage: React.FC = () => {
|
||||
const defaultRelayInput = React.useMemo(() => 'wss://', [])
|
||||
const { updateRelayItem, addRelayItem, removeRelayItem, relays, relayPool } =
|
||||
@ -30,6 +45,7 @@ export const RelaysPage: React.FC = () => {
|
||||
const theme = useTheme()
|
||||
const bottomSheetAddRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetEditRef = React.useRef<RBSheet>(null)
|
||||
const bottomSheetResilenseRef = React.useRef<RBSheet>(null)
|
||||
const [selectedRelay, setSelectedRelay] = useState<Relay>()
|
||||
const [addRelayInput, setAddRelayInput] = useState<string>(defaultRelayInput)
|
||||
const [showNotification, setShowNotification] = useState<string>()
|
||||
@ -119,13 +135,13 @@ export const RelaysPage: React.FC = () => {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const myRelays = relays
|
||||
.filter((relay) => !defaultRelays.includes(relay.url))
|
||||
.sort((a, b) => {
|
||||
if (a.url > b.url) return 1
|
||||
if (a.url < b.url) return -1
|
||||
return 0
|
||||
})
|
||||
const relayList = relays.sort((a, b) => {
|
||||
if (a.url > b.url) return 1
|
||||
if (a.url < b.url) return -1
|
||||
return 0
|
||||
})
|
||||
|
||||
const myRelays = relayList.filter((relay) => !defaultRelays.includes(relay.url))
|
||||
|
||||
const renderItem: ListRenderItem<Relay> = ({ item, index }) => {
|
||||
return (
|
||||
@ -162,18 +178,86 @@ export const RelaysPage: React.FC = () => {
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
const renderResilienceItem: (item: string, index: number, type?: string) => JSX.Element = (
|
||||
item,
|
||||
index,
|
||||
type,
|
||||
) => {
|
||||
return (
|
||||
<List.Item
|
||||
title={t('relaysPage.relayName')}
|
||||
key={index}
|
||||
title={item.split('wss://')[1]?.split('/')[0]}
|
||||
left={() => (
|
||||
<MaterialCommunityIcons
|
||||
style={styles.relayColor}
|
||||
name='circle'
|
||||
color={relayToColor(item)}
|
||||
/>
|
||||
)}
|
||||
right={() => (
|
||||
<>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.globalFeed')}</Text>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.active')}</Text>
|
||||
{type === 'centralized' && (
|
||||
<Text style={[styles.smallRelay, { color: theme.colors.errorContainer }]}>
|
||||
{relayPool?.resilientAssignation.centralizedRelays[item] &&
|
||||
t('relaysPage.centralized')}
|
||||
</Text>
|
||||
)}
|
||||
{type === 'small' && (
|
||||
<Text style={[styles.smallRelay, { color: theme.colors.error }]}>
|
||||
{relayPool?.resilientAssignation.smallRelays[item] && t('relaysPage.small')}
|
||||
</Text>
|
||||
)}
|
||||
<Text>
|
||||
{type === undefined && relayPool?.resilientAssignation.resilientRelays[item]?.length}
|
||||
{type === 'small' && relayPool?.resilientAssignation.smallRelays[item]?.length}
|
||||
{type === 'centralized' &&
|
||||
relayPool?.resilientAssignation.centralizedRelays[item]?.length}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScrollView horizontal={false}>
|
||||
<View style={styles.titleWrapper}>
|
||||
<View style={styles.title}>
|
||||
<Text style={styles.title} variant='titleMedium'>
|
||||
{t('relaysPage.resilienceMode')}
|
||||
</Text>
|
||||
<IconButton
|
||||
style={styles.titleAction}
|
||||
icon='question'
|
||||
size={20}
|
||||
onPress={() => bottomSheetResilenseRef.current?.open()}
|
||||
/>
|
||||
</View>
|
||||
<Divider />
|
||||
</View>
|
||||
<List.Item
|
||||
title={t('relaysPage.relayName')}
|
||||
right={() => <Text style={styles.listHeader}>{t('relaysPage.contacts')}</Text>}
|
||||
/>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={Object.keys(relayPool?.resilientAssignation.resilientRelays ?? {})}
|
||||
renderItem={(data) => renderResilienceItem(data.item, data.index)}
|
||||
/>
|
||||
<View style={styles.titleWrapper}>
|
||||
<Divider />
|
||||
</View>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={Object.keys(relayPool?.resilientAssignation.centralizedRelays ?? {})}
|
||||
renderItem={(data) => renderResilienceItem(data.item, data.index, 'centralized')}
|
||||
/>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={Object.keys(relayPool?.resilientAssignation.smallRelays ?? {})}
|
||||
renderItem={(data) => renderResilienceItem(data.item, data.index, 'small')}
|
||||
/>
|
||||
{myRelays.length > 0 && (
|
||||
<>
|
||||
<View style={styles.titleWrapper}>
|
||||
@ -182,6 +266,15 @@ export const RelaysPage: React.FC = () => {
|
||||
</Text>
|
||||
<Divider />
|
||||
</View>
|
||||
<List.Item
|
||||
title={t('relaysPage.relayName')}
|
||||
right={() => (
|
||||
<>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.globalFeed')}</Text>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.active')}</Text>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={myRelays}
|
||||
@ -195,6 +288,15 @@ export const RelaysPage: React.FC = () => {
|
||||
</Text>
|
||||
<Divider />
|
||||
</View>
|
||||
<List.Item
|
||||
title={t('relaysPage.relayName')}
|
||||
right={() => (
|
||||
<>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.globalFeed')}</Text>
|
||||
<Text style={styles.listHeader}>{t('relaysPage.active')}</Text>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<FlatList
|
||||
showsVerticalScrollIndicator={false}
|
||||
data={defaultRelays.map(
|
||||
@ -227,6 +329,18 @@ export const RelaysPage: React.FC = () => {
|
||||
{t(`relaysPage.notifications.${showNotification}`)}
|
||||
</Snackbar>
|
||||
)}
|
||||
<RBSheet
|
||||
ref={bottomSheetResilenseRef}
|
||||
closeOnDragDown={true}
|
||||
customStyles={rbSheetCustomStyles}
|
||||
>
|
||||
<View style={styles.resilienceDrawer}>
|
||||
<Text variant='headlineSmall'>{t('relaysPage.resilienceTitle')}</Text>
|
||||
<Text variant='bodyMedium'>{t('relaysPage.resilienceDescription')}</Text>
|
||||
<Text variant='titleMedium'>{t('relaysPage.resilienceCategories')}</Text>
|
||||
<Text variant='bodyMedium'>{t('relaysPage.resilienceCategoriesDescription')}</Text>
|
||||
</View>
|
||||
</RBSheet>
|
||||
<RBSheet ref={bottomSheetAddRef} closeOnDragDown={true} customStyles={rbSheetCustomStyles}>
|
||||
<View style={styles.addRelay}>
|
||||
<View style={styles.bottomDrawerButton}>
|
||||
@ -294,6 +408,12 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
title: {
|
||||
marginBottom: 8,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
titleAction: {
|
||||
marginTop: -5,
|
||||
marginBottom: -10,
|
||||
},
|
||||
bottomDrawerButton: {
|
||||
paddingBottom: 16,
|
||||
@ -341,6 +461,16 @@ const styles = StyleSheet.create({
|
||||
marginBottom: 26,
|
||||
marginTop: 26,
|
||||
},
|
||||
centralizedRelay: {
|
||||
paddingRight: 10,
|
||||
},
|
||||
smallRelay: {
|
||||
paddingRight: 10,
|
||||
},
|
||||
resilienceDrawer: {
|
||||
height: 600,
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
})
|
||||
|
||||
export default RelaysPage
|
||||
|
@ -1,6 +1,14 @@
|
||||
// import { spawnThread } from 'react-native-multithreading'
|
||||
import { signEvent, validateEvent, Event } from '../Events'
|
||||
import RelayPoolModule from '../../Native/WebsocketModule'
|
||||
import { QuickSQLiteConnection } from 'react-native-quick-sqlite'
|
||||
import { median, randomInt } from '../../../Functions/NativeFunctions'
|
||||
import {
|
||||
activateResilientRelays,
|
||||
createResilientRelay,
|
||||
desactivateResilientRelays,
|
||||
} from '../../../Functions/DatabaseFunctions/Relays'
|
||||
import { getNoteRelaysPresence } from '../../../Functions/DatabaseFunctions/NotesRelays'
|
||||
|
||||
export interface RelayFilters {
|
||||
ids?: string[]
|
||||
@ -17,14 +25,42 @@ export interface RelayMessage {
|
||||
data: string
|
||||
}
|
||||
|
||||
export interface ResilientAssignation {
|
||||
resilientRelays: Record<string, string[]>
|
||||
smallRelays: Record<string, string[]>
|
||||
centralizedRelays: Record<string, string[]>
|
||||
}
|
||||
|
||||
export const fallbackRelays = [
|
||||
'wss://brb.io',
|
||||
'wss://damus.io',
|
||||
'wss://nostr-pub.wellorder.net',
|
||||
'wss://nostr.swiss-enigma.ch',
|
||||
'wss://nostr.onsats.org',
|
||||
'wss://nostr-pub.semisol.dev',
|
||||
'wss://nostr.openchain.fr',
|
||||
'wss://relay.nostr.info',
|
||||
'wss://nostr.oxtr.dev',
|
||||
'wss://nostr.ono.re',
|
||||
'wss://relay.grunch.dev',
|
||||
'wss://nostr.developer.li',
|
||||
]
|
||||
|
||||
class RelayPool {
|
||||
constructor(privateKey?: string) {
|
||||
this.privateKey = privateKey
|
||||
this.subscriptions = {}
|
||||
this.resilientAssignation = {
|
||||
resilientRelays: {},
|
||||
smallRelays: {},
|
||||
centralizedRelays: {},
|
||||
fallback: {},
|
||||
}
|
||||
}
|
||||
|
||||
private readonly privateKey?: string
|
||||
private subscriptions: Record<string, string[]>
|
||||
public resilientAssignation: ResilientAssignation
|
||||
|
||||
private readonly send: (message: object, globalFeed?: boolean) => void = async (
|
||||
message,
|
||||
@ -39,6 +75,89 @@ class RelayPool {
|
||||
RelayPoolModule.connect(publicKey, onEventId)
|
||||
}
|
||||
|
||||
public readonly resilientMode: (db: QuickSQLiteConnection, publicKey: string) => void = async (
|
||||
db,
|
||||
) => {
|
||||
await desactivateResilientRelays(db)
|
||||
// Get relays with contacts' pubkeys with at least one event found, randomly sorted
|
||||
const relaysPresence: Record<string, string[]> = await getNoteRelaysPresence(db)
|
||||
// Median of users per relay
|
||||
const medianUsage = median(
|
||||
Object.keys(relaysPresence).map((relay) => relaysPresence[relay].length),
|
||||
)
|
||||
|
||||
// Sort relays by abs distance from the mediam
|
||||
const relaysByPresence = Object.keys(relaysPresence).sort((n1: string, n2: string) => {
|
||||
return (
|
||||
Math.abs(relaysPresence[n1].length - medianUsage) -
|
||||
Math.abs(relaysPresence[n2].length - medianUsage)
|
||||
)
|
||||
})
|
||||
// Get top5 relays closer to the mediam
|
||||
const medianRelays = relaysByPresence.slice(0, 5)
|
||||
|
||||
// Set helpers
|
||||
let biggestRelayLenght = 0
|
||||
this.resilientAssignation.resilientRelays = {}
|
||||
const allocatedUsers: string[] = []
|
||||
medianRelays.forEach((relayUrl) => {
|
||||
this.resilientAssignation.resilientRelays[relayUrl] = []
|
||||
const length = relaysPresence[relayUrl].length
|
||||
if (length > biggestRelayLenght) biggestRelayLenght = length
|
||||
})
|
||||
|
||||
// Iterate over the N index of top5 relay list removing identical pubkey from others
|
||||
for (let index = 0; index < biggestRelayLenght - 1; index++) {
|
||||
medianRelays.forEach((relayUrl) => {
|
||||
const pubKey = relaysPresence[relayUrl][index]
|
||||
if (pubKey && !allocatedUsers.includes(pubKey)) {
|
||||
allocatedUsers.push(pubKey)
|
||||
this.resilientAssignation.resilientRelays[relayUrl].push(pubKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Iterate over remaining relays and assigns as much remaining users as possible
|
||||
relaysByPresence.slice(5, relaysByPresence.length).forEach((relayUrl) => {
|
||||
relaysPresence[relayUrl].forEach((pubKey) => {
|
||||
if (!allocatedUsers.includes(pubKey)) {
|
||||
allocatedUsers.push(pubKey)
|
||||
if (relaysPresence[relayUrl].length > medianUsage) {
|
||||
if (!this.resilientAssignation.centralizedRelays[relayUrl])
|
||||
this.resilientAssignation.centralizedRelays[relayUrl] = []
|
||||
this.resilientAssignation.centralizedRelays[relayUrl].push(pubKey)
|
||||
} else {
|
||||
if (!this.resilientAssignation.smallRelays[relayUrl])
|
||||
this.resilientAssignation.smallRelays[relayUrl] = []
|
||||
this.resilientAssignation.smallRelays[relayUrl].push(pubKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Target list size is 5, adds random relays from a fallback list
|
||||
const resilientUrls = [
|
||||
...Object.keys(this.resilientAssignation.resilientRelays),
|
||||
...Object.keys(this.resilientAssignation.centralizedRelays),
|
||||
...Object.keys(this.resilientAssignation.smallRelays),
|
||||
]
|
||||
while (resilientUrls.length < 5) {
|
||||
let fallbackRelay = ''
|
||||
while (fallbackRelay === '') {
|
||||
const randomRelayIndex = randomInt(0, fallbackRelays.length - 1)
|
||||
if (!resilientUrls.includes(fallbackRelays[randomRelayIndex])) {
|
||||
fallbackRelay = fallbackRelays[randomRelayIndex]
|
||||
}
|
||||
}
|
||||
resilientUrls.push(fallbackRelay)
|
||||
this.resilientAssignation.centralizedRelays[fallbackRelay] = []
|
||||
}
|
||||
|
||||
// Stores in DB
|
||||
resilientUrls.forEach(async (relayUrl) => await createResilientRelay(db, relayUrl))
|
||||
activateResilientRelays(db, resilientUrls)
|
||||
}
|
||||
|
||||
public readonly add: (relayUrl: string, callback?: () => void) => void = async (
|
||||
relayUrl,
|
||||
callback = () => {},
|
||||
|
Loading…
Reference in New Issue
Block a user