feat: restrict storage
This commit is contained in:
parent
a2f3951efa
commit
339f2b70c1
3 changed files with 69 additions and 7 deletions
|
|
@ -11,7 +11,10 @@ from .crud import (
|
||||||
get_config_for_all_active_relays,
|
get_config_for_all_active_relays,
|
||||||
get_event,
|
get_event,
|
||||||
get_events,
|
get_events,
|
||||||
|
get_prunable_events,
|
||||||
|
get_storage_for_public_key,
|
||||||
mark_events_deleted,
|
mark_events_deleted,
|
||||||
|
prune_old_events,
|
||||||
)
|
)
|
||||||
from .models import ClientConfig, NostrEvent, NostrEventType, NostrFilter, RelayConfig
|
from .models import ClientConfig, NostrEvent, NostrEventType, NostrFilter, RelayConfig
|
||||||
|
|
||||||
|
|
@ -156,6 +159,12 @@ class NostrClientConnection:
|
||||||
await self._send_msg(resp_nip20)
|
await self._send_msg(resp_nip20)
|
||||||
return None
|
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:
|
try:
|
||||||
if e.is_replaceable_event():
|
if e.is_replaceable_event():
|
||||||
await delete_events(
|
await delete_events(
|
||||||
|
|
@ -237,6 +246,29 @@ class NostrClientConnection:
|
||||||
|
|
||||||
return True, ""
|
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:
|
def _exceeded_max_events_per_second(self) -> bool:
|
||||||
if self.client_config.max_events_per_second == 0:
|
if self.client_config.max_events_per_second == 0:
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
31
crud.py
31
crud.py
|
|
@ -1,7 +1,5 @@
|
||||||
import json
|
import json
|
||||||
from typing import Any, List, Optional
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
|
|
||||||
from . import db
|
from . import db
|
||||||
from .models import NostrEvent, NostrFilter, NostrRelay, RelayConfig
|
from .models import NostrEvent, NostrFilter, NostrRelay, RelayConfig
|
||||||
|
|
@ -126,8 +124,19 @@ async def get_storage_for_public_key(relay_id: str, pubkey: str) -> int:
|
||||||
if not row:
|
if not row:
|
||||||
return 0
|
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):
|
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))
|
await db.execute(query, tuple(values))
|
||||||
#todo: delete tags
|
#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):
|
async def delete_all_events(relay_id: str):
|
||||||
query = "DELETE from nostrrelay.events WHERE relay_id = ?"
|
query = "DELETE from nostrrelay.events WHERE relay_id = ?"
|
||||||
|
|
|
||||||
11
models.py
11
models.py
|
|
@ -24,7 +24,8 @@ class ClientConfig(BaseModel):
|
||||||
created_at_seconds_future = Field(0, alias="createdAtSecondsFuture")
|
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")
|
free_storage_unit = Field("MB", alias="freeStorageUnit")
|
||||||
full_storage_action = Field("prune", alias="fullStorageAction")
|
full_storage_action = Field("prune", alias="fullStorageAction")
|
||||||
|
|
||||||
|
|
@ -48,11 +49,17 @@ class ClientConfig(BaseModel):
|
||||||
def created_at_in_future(self) -> int:
|
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
|
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:
|
class Config:
|
||||||
allow_population_by_field_name = True
|
allow_population_by_field_name = True
|
||||||
|
|
||||||
class RelayConfig(ClientConfig):
|
class RelayConfig(ClientConfig):
|
||||||
is_paid_relay = Field(False, alias="isPaidRelay")
|
|
||||||
wallet = Field("")
|
wallet = Field("")
|
||||||
cost_to_join = Field(0, alias="costToJoin")
|
cost_to_join = Field(0, alias="costToJoin")
|
||||||
free_storage = Field(0, alias="freeStorage")
|
free_storage = Field(0, alias="freeStorage")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue