feat: limit max events per second
This commit is contained in:
parent
bddab70677
commit
868e02d3c2
3 changed files with 64 additions and 14 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import time
|
||||
from typing import Any, Awaitable, Callable, List, Optional
|
||||
|
||||
from fastapi import WebSocket
|
||||
|
|
@ -89,6 +90,9 @@ class NostrClientConnection:
|
|||
self.broadcast_event: Optional[Callable[[NostrClientConnection, NostrEvent], Awaitable[None]]] = None
|
||||
self.get_client_config: Optional[Callable[[], ClientConfig]] = None
|
||||
|
||||
self._last_event_timestamp = 0 # in seconds
|
||||
self._event_count_per_timestamp = 0
|
||||
|
||||
async def start(self):
|
||||
await self.websocket.accept()
|
||||
while True:
|
||||
|
|
@ -146,6 +150,11 @@ class NostrClientConnection:
|
|||
async def _handle_event(self, e: NostrEvent):
|
||||
resp_nip20: List[Any] = ["OK", e.id]
|
||||
|
||||
if self._exceeded_max_events_per_second():
|
||||
resp_nip20 += [False, f"Exceeded max events per second limit'!"]
|
||||
await self._send_msg(resp_nip20)
|
||||
return None
|
||||
|
||||
if not self.client_config.is_author_allowed(e.pubkey):
|
||||
resp_nip20 += [False, f"Public key '{e.pubkey}' is not allowed in relay '{self.relay_id}'!"]
|
||||
await self._send_msg(resp_nip20)
|
||||
|
|
@ -197,7 +206,7 @@ class NostrClientConnection:
|
|||
|
||||
async def _handle_request(self, subscription_id: str, filter: NostrFilter) -> List:
|
||||
filter.subscription_id = subscription_id
|
||||
self.remove_filter(subscription_id)
|
||||
self._remove_filter(subscription_id)
|
||||
if self._can_add_filter():
|
||||
return [["NOTICE", f"Maximum number of filters ({self.client_config.max_client_filters}) exceeded."]]
|
||||
|
||||
|
|
@ -211,11 +220,24 @@ class NostrClientConnection:
|
|||
serialized_events.append(resp_nip15)
|
||||
return serialized_events
|
||||
|
||||
def remove_filter(self, subscription_id: str):
|
||||
def _remove_filter(self, subscription_id: str):
|
||||
self.filters = [f for f in self.filters if f.subscription_id != subscription_id]
|
||||
|
||||
def _handle_close(self, subscription_id: str):
|
||||
self.remove_filter(subscription_id)
|
||||
self._remove_filter(subscription_id)
|
||||
|
||||
def _can_add_filter(self) -> bool:
|
||||
return self.client_config.max_client_filters != 0 and len(self.filters) >= self.client_config.max_client_filters
|
||||
|
||||
def _exceeded_max_events_per_second(self) -> bool:
|
||||
if self.client_config.max_events_per_second == 0:
|
||||
return False
|
||||
|
||||
current_time = round(time.time())
|
||||
if self._last_event_timestamp == current_time:
|
||||
self._event_count_per_timestamp += 1
|
||||
else:
|
||||
self._last_event_timestamp = current_time
|
||||
self._event_count_per_timestamp = 0
|
||||
|
||||
return self._event_count_per_timestamp > self.client_config.max_events_per_second
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from secp256k1 import PublicKey
|
|||
class ClientConfig(BaseModel):
|
||||
max_client_filters = Field(0, alias="maxClientFilters")
|
||||
limit_per_filter = Field(1000, alias="limitPerFilter")
|
||||
max_events_per_second = Field(0, alias="maxEventsPerSecond")
|
||||
allowed_public_keys = Field([], alias="allowedPublicKeys")
|
||||
blocked_public_keys = Field([], alias="blockedPublicKeys")
|
||||
|
||||
|
|
|
|||
|
|
@ -168,18 +168,18 @@
|
|||
></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-icon name="info" class="cursor-pointer">
|
||||
<q-tooltip>
|
||||
Maximum number of events to be returned in the initial query
|
||||
(default 1000)
|
||||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge
|
||||
v-if="relay.config.limitPerFilter == 0"
|
||||
color="green"
|
||||
class="float-left"
|
||||
><span>No Limit</span>
|
||||
</q-badge>
|
||||
<q-badge v-else color="yellow" text-color="black" class="float-left"
|
||||
><span
|
||||
>Maximum number of events to be returned in the initial query
|
||||
(default 1000)</span
|
||||
>
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
|
|
@ -194,14 +194,41 @@
|
|||
></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-badge
|
||||
v-if="relay.config.maxClientFilters == 0"
|
||||
color="green"
|
||||
class="float-left"
|
||||
<q-icon name="info" class="cursor-pointer">
|
||||
<q-tooltip>
|
||||
Limit the number of filters that a client can have. Prevents
|
||||
relay from being abused by clients with extremly high number of
|
||||
fiters.
|
||||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge v-if="relay.config.maxClientFilters == 0" color="green"
|
||||
><span>Unlimited Filters</span>
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col-3 q-pr-lg">Max events per second:</div>
|
||||
<div class="col-3 q-pr-lg">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="relay.config.maxEventsPerSecond"
|
||||
type="number"
|
||||
min="0"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-icon name="info" class="cursor-pointer">
|
||||
<q-tooltip>
|
||||
Limits the rate at which events are accepted by the relay.
|
||||
Prevent clients from clogging the relay.
|
||||
</q-tooltip></q-icon
|
||||
>
|
||||
<q-badge v-if="relay.config.maxEventsPerSecond == 0" color="green"
|
||||
><span>No Limit</span>
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-tab-panel>
|
||||
<q-tab-panel name="allowed">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue