mirror of
https://github.com/waku-org/nwaku.git
synced 2025-01-16 18:06:45 +00:00
ba418ab5ba
* DOS protection of non relay protocols - rate limit phase3: - Enhanced TokenBucket to be able to add compensation tokens based on previous usage percentage, - per peer rate limiter 'PeerRateLimier' applied on waku_filter_v2 with opinionated default of acceptable request rate - Add traffic metrics to filter message push - RequestRateLimiter added to combine simple token bucket limiting of request numbers but consider per peer usage over time and prevent some peers to over use the service (although currently rule violating peers will not be disconnected by this time only their requests will get not served) - TimedMap utility created (inspired and taken from libp2p TimedCache) which serves as forgiving feature for peers had been overusing the service. - Added more tests - Fix rebase issues - Applied new RequestRateLimiter for store and legacy_store and lightpush * Incorporate review comments, typos, file/class naming and placement changes. * Add issue link reference of the original issue with nim-chronos TokenBucket * Make TimedEntry of TimedMap private and not mixable with similar named in libp2p * Fix review comments, renamings, const instead of values and more comments.
160 lines
4.8 KiB
Nim
160 lines
4.8 KiB
Nim
## Waku Store protocol for historical messaging support.
|
|
## See spec for more details:
|
|
## https://github.com/vacp2p/specs/blob/master/specs/waku/v2/waku-store.md
|
|
{.push raises: [].}
|
|
|
|
import
|
|
std/options,
|
|
results,
|
|
chronicles,
|
|
chronos,
|
|
bearssl/rand,
|
|
libp2p/crypto/crypto,
|
|
libp2p/protocols/protocol,
|
|
libp2p/protobuf/minprotobuf,
|
|
libp2p/stream/connection,
|
|
metrics
|
|
import
|
|
../waku_core,
|
|
../node/peer_manager,
|
|
./common,
|
|
./rpc,
|
|
./rpc_codec,
|
|
./protocol_metrics,
|
|
../common/rate_limit/request_limiter
|
|
|
|
logScope:
|
|
topics = "waku legacy store"
|
|
|
|
const MaxMessageTimestampVariance* = getNanoSecondTime(20)
|
|
# 20 seconds maximum allowable sender timestamp "drift"
|
|
|
|
type HistoryQueryHandler* =
|
|
proc(req: HistoryQuery): Future[HistoryResult] {.async, gcsafe.}
|
|
|
|
type WakuStore* = ref object of LPProtocol
|
|
peerManager: PeerManager
|
|
rng: ref rand.HmacDrbgContext
|
|
queryHandler*: HistoryQueryHandler
|
|
requestRateLimiter*: RequestRateLimiter
|
|
|
|
## Protocol
|
|
|
|
proc handleLegacyQueryRequest(
|
|
self: WakuStore, requestor: PeerId, raw_request: seq[byte]
|
|
): Future[seq[byte]] {.async.} =
|
|
let decodeRes = HistoryRPC.decode(raw_request)
|
|
if decodeRes.isErr():
|
|
error "failed to decode rpc", peerId = requestor
|
|
waku_legacy_store_errors.inc(labelValues = [decodeRpcFailure])
|
|
# TODO: Return (BAD_REQUEST, cause: "decode rpc failed")
|
|
return
|
|
|
|
let reqRpc = decodeRes.value
|
|
|
|
if reqRpc.query.isNone():
|
|
error "empty query rpc", peerId = requestor, requestId = reqRpc.requestId
|
|
waku_legacy_store_errors.inc(labelValues = [emptyRpcQueryFailure])
|
|
# TODO: Return (BAD_REQUEST, cause: "empty query")
|
|
return
|
|
|
|
let
|
|
requestId = reqRpc.requestId
|
|
request = reqRpc.query.get().toAPI()
|
|
|
|
info "received history query",
|
|
peerId = requestor, requestId = requestId, query = request
|
|
waku_legacy_store_queries.inc()
|
|
|
|
var responseRes: HistoryResult
|
|
try:
|
|
responseRes = await self.queryHandler(request)
|
|
except Exception:
|
|
error "history query failed",
|
|
peerId = requestor, requestId = requestId, error = getCurrentExceptionMsg()
|
|
|
|
let error = HistoryError(kind: HistoryErrorKind.UNKNOWN).toRPC()
|
|
let response = HistoryResponseRPC(error: error)
|
|
return HistoryRPC(requestId: requestId, response: some(response)).encode().buffer
|
|
|
|
if responseRes.isErr():
|
|
error "history query failed",
|
|
peerId = requestor, requestId = requestId, error = responseRes.error
|
|
|
|
let response = responseRes.toRPC()
|
|
return HistoryRPC(requestId: requestId, response: some(response)).encode().buffer
|
|
|
|
let response = responseRes.toRPC()
|
|
|
|
info "sending history response",
|
|
peerId = requestor, requestId = requestId, messages = response.messages.len
|
|
|
|
return HistoryRPC(requestId: requestId, response: some(response)).encode().buffer
|
|
|
|
proc initProtocolHandler(ws: WakuStore) =
|
|
let rejectResponseBuf = HistoryRPC(
|
|
## We will not copy and decode RPC buffer from stream only for requestId
|
|
## in reject case as it is comparably too expensive and opens possible
|
|
## attack surface
|
|
requestId: "N/A",
|
|
response: some(
|
|
HistoryResponseRPC(
|
|
error: HistoryError(kind: HistoryErrorKind.TOO_MANY_REQUESTS).toRPC()
|
|
)
|
|
),
|
|
).encode().buffer
|
|
|
|
proc handler(conn: Connection, proto: string) {.async, closure.} =
|
|
var resBuf: seq[byte]
|
|
ws.requestRateLimiter.checkUsageLimit(WakuLegacyStoreCodec, conn):
|
|
let readRes = catch:
|
|
await conn.readLp(DefaultMaxRpcSize.int)
|
|
|
|
let reqBuf = readRes.valueOr:
|
|
error "Connection read error", error = error.msg
|
|
return
|
|
|
|
waku_service_network_bytes.inc(
|
|
amount = reqBuf.len().int64, labelValues = [WakuLegacyStoreCodec, "in"]
|
|
)
|
|
|
|
resBuf = await ws.handleLegacyQueryRequest(conn.peerId, reqBuf)
|
|
do:
|
|
debug "Legacy store query request rejected due rate limit exceeded",
|
|
peerId = conn.peerId, limit = $ws.requestRateLimiter.setting
|
|
resBuf = rejectResponseBuf
|
|
|
|
let writeRes = catch:
|
|
await conn.writeLp(resBuf)
|
|
|
|
if writeRes.isErr():
|
|
error "Connection write error", error = writeRes.error.msg
|
|
return
|
|
|
|
waku_service_network_bytes.inc(
|
|
amount = resBuf.len().int64, labelValues = [WakuLegacyStoreCodec, "out"]
|
|
)
|
|
|
|
ws.handler = handler
|
|
ws.codec = WakuLegacyStoreCodec
|
|
|
|
proc new*(
|
|
T: type WakuStore,
|
|
peerManager: PeerManager,
|
|
rng: ref rand.HmacDrbgContext,
|
|
queryHandler: HistoryQueryHandler,
|
|
rateLimitSetting: Option[RateLimitSetting] = none[RateLimitSetting](),
|
|
): T =
|
|
# Raise a defect if history query handler is nil
|
|
if queryHandler.isNil():
|
|
raise newException(NilAccessDefect, "history query handler is nil")
|
|
|
|
let ws = WakuStore(
|
|
rng: rng,
|
|
peerManager: peerManager,
|
|
queryHandler: queryHandler,
|
|
requestRateLimiter: newRequestRateLimiter(rateLimitSetting),
|
|
)
|
|
ws.initProtocolHandler()
|
|
ws
|