feat: restrict storage

This commit is contained in:
Vlad Stan 2023-02-10 11:43:18 +02:00
parent a2f3951efa
commit 339f2b70c1
3 changed files with 69 additions and 7 deletions

View file

@ -11,7 +11,10 @@ from .crud import (
get_config_for_all_active_relays,
get_event,
get_events,
get_prunable_events,
get_storage_for_public_key,
mark_events_deleted,
prune_old_events,
)
from .models import ClientConfig, NostrEvent, NostrEventType, NostrFilter, RelayConfig
@ -156,6 +159,12 @@ class NostrClientConnection:
await self._send_msg(resp_nip20)
return None
valid, message = await self._validate_storage(e)
if not valid:
resp_nip20 += [valid, message]
await self._send_msg(resp_nip20)
return None
try:
if e.is_replaceable_event():
await delete_events(
@ -237,6 +246,29 @@ class NostrClientConnection:
return True, ""
async def _validate_storage(self, e: NostrEvent) -> Tuple[bool, str]:
if self.client_config.free_storage_value == 0:
if not self.client_config.is_paid_relay:
return False, "Cannot write event, relay is read-only"
# todo: handeld paid paid plan
return True, "Temp OK"
stored_bytes = await get_storage_for_public_key(self.relay_id, e.pubkey)
if self.client_config.is_paid_relay:
# todo: handeld paid paid plan
return True, "Temp OK"
if (stored_bytes + e.size_bytes) <= self.client_config.free_storage_bytes_value:
return True, ""
if self.client_config.full_storage_action == "block":
return False, f"Cannot write event, no more storage available for public key: '{e.pubkey}'"
await prune_old_events(self.relay_id, e.pubkey, e.size_bytes)
return True, ""
def _exceeded_max_events_per_second(self) -> bool:
if self.client_config.max_events_per_second == 0:
return False

33
crud.py
View file

@ -1,7 +1,5 @@
import json
from typing import Any, List, Optional
from lnbits.helpers import urlsafe_short_hash
from typing import Any, List, Optional, Tuple
from . import db
from .models import NostrEvent, NostrFilter, NostrRelay, RelayConfig
@ -121,13 +119,24 @@ async def get_event(relay_id: str, id: str) -> Optional[NostrEvent]:
async def get_storage_for_public_key(relay_id: str, pubkey: str) -> int:
"""Returns the storage space in bytes for all the events of a public key. Deleted events are also counted"""
row = await db.fetchone("SELECT SUM(size) FROM nostrrelay.events WHERE relay_id = ? AND pubkey = ?", (relay_id, pubkey,))
if not row:
return 0
return row["sum"]
return round(row["sum"])
async def get_prunable_events(relay_id: str, pubkey: str) -> List[Tuple[str, int]]:
""" Return the oldest 10 000 events. Only the `id` and the size are returned, so the data size should be small"""
query = """
SELECT id, size FROM nostrrelay.events
WHERE relay_id = ? AND pubkey = ?
ORDER BY created_at ASC LIMIT 10000
"""
rows = await db.fetchall(query, (relay_id, pubkey))
return [(r["id"], r["size"]) for r in rows]
async def mark_events_deleted(relay_id: str, filter: NostrFilter):
@ -146,6 +155,20 @@ async def delete_events(relay_id: str, filter: NostrFilter):
await db.execute(query, tuple(values))
#todo: delete tags
async def prune_old_events(relay_id: str, pubkey: str, space_to_regain: int):
prunable_events = await get_prunable_events(relay_id, pubkey)
prunable_event_ids = []
size = 0
for pe in prunable_events:
prunable_event_ids.append(pe[0])
size += pe[1]
if size > space_to_regain:
break
await delete_events(relay_id, NostrFilter(ids=prunable_event_ids))
async def delete_all_events(relay_id: str):
query = "DELETE from nostrrelay.events WHERE relay_id = ?"

View file

@ -24,7 +24,8 @@ class ClientConfig(BaseModel):
created_at_seconds_future = Field(0, alias="createdAtSecondsFuture")
free_storage_value = Field("1", alias="freeStorageValue")
is_paid_relay = Field(False, alias="isPaidRelay")
free_storage_value = Field(1, alias="freeStorageValue")
free_storage_unit = Field("MB", alias="freeStorageUnit")
full_storage_action = Field("prune", alias="fullStorageAction")
@ -48,11 +49,17 @@ class ClientConfig(BaseModel):
def created_at_in_future(self) -> int:
return self.created_at_days_future * 86400 + self.created_at_hours_future * 3600 + self.created_at_minutes_future * 60 + self.created_at_seconds_future
@property
def free_storage_bytes_value(self):
value = self.free_storage_value * 1024
if self.free_storage_unit == "MB":
value *= 1024
return value
class Config:
allow_population_by_field_name = True
class RelayConfig(ClientConfig):
is_paid_relay = Field(False, alias="isPaidRelay")
wallet = Field("")
cost_to_join = Field(0, alias="costToJoin")
free_storage = Field(0, alias="freeStorage")