mirror of https://github.com/waku-org/nwaku.git
197 lines
6.9 KiB
Nim
197 lines
6.9 KiB
Nim
## This module is in charge of taking care of the messages that this node is expecting to
|
|
## receive and is backed by store-v3 requests to get an additional degree of certainty
|
|
##
|
|
|
|
import std/[tables, sequtils, options]
|
|
import chronos, chronicles, libp2p/utility
|
|
import
|
|
../../waku_core,
|
|
./delivery_callback,
|
|
./subscriptions_observer,
|
|
../../waku_store/[client, common],
|
|
../../waku_filter_v2/client,
|
|
../../waku_core/topics
|
|
|
|
const StoreCheckPeriod = chronos.minutes(5) ## How often to perform store queries
|
|
|
|
const MaxMessageLife = chronos.minutes(7) ## Max time we will keep track of rx messages
|
|
|
|
const PruneOldMsgsPeriod = chronos.minutes(1)
|
|
|
|
const DelayExtra* = chronos.seconds(5)
|
|
## Additional security time to overlap the missing messages queries
|
|
|
|
type TupleHashAndMsg = tuple[hash: WakuMessageHash, msg: WakuMessage]
|
|
|
|
type RecvMessage = object
|
|
msgHash: WakuMessageHash
|
|
rxTime: Timestamp
|
|
## timestamp of the rx message. We will not keep the rx messages forever
|
|
|
|
type RecvMonitor* = ref object of SubscriptionObserver
|
|
topicsInterest: Table[PubsubTopic, seq[ContentTopic]]
|
|
## Tracks message verification requests and when was the last time a
|
|
## pubsub topic was verified for missing messages
|
|
## The key contains pubsub-topics
|
|
|
|
storeClient: WakuStoreClient
|
|
deliveryCb: DeliveryFeedbackCallback
|
|
|
|
recentReceivedMsgs: seq[RecvMessage]
|
|
|
|
msgCheckerHandler: Future[void] ## allows to stop the msgChecker async task
|
|
msgPrunerHandler: Future[void] ## removes too old messages
|
|
|
|
startTimeToCheck: Timestamp
|
|
endTimeToCheck: Timestamp
|
|
|
|
proc getMissingMsgsFromStore(
|
|
self: RecvMonitor, msgHashes: seq[WakuMessageHash]
|
|
): Future[Result[seq[TupleHashAndMsg], string]] {.async.} =
|
|
let storeResp: StoreQueryResponse = (
|
|
await self.storeClient.queryToAny(
|
|
StoreQueryRequest(includeData: true, messageHashes: msgHashes)
|
|
)
|
|
).valueOr:
|
|
return err("getMissingMsgsFromStore: " & $error)
|
|
|
|
let otherwiseMsg = WakuMessage()
|
|
## message to be returned if the Option message is none
|
|
return ok(
|
|
storeResp.messages.mapIt((hash: it.messageHash, msg: it.message.get(otherwiseMsg)))
|
|
)
|
|
|
|
proc performDeliveryFeedback(
|
|
self: RecvMonitor,
|
|
success: DeliverySuccess,
|
|
dir: DeliveryDirection,
|
|
comment: string,
|
|
msgHash: WakuMessageHash,
|
|
msg: WakuMessage,
|
|
) {.gcsafe, raises: [].} =
|
|
## This procs allows to bring delivery feedback to the API client
|
|
## It requires a 'deliveryCb' to be registered beforehand.
|
|
if self.deliveryCb.isNil():
|
|
error "deliveryCb is nil in performDeliveryFeedback",
|
|
success, dir, comment, msg_hash
|
|
return
|
|
|
|
debug "recv monitor performDeliveryFeedback",
|
|
success, dir, comment, msg_hash = shortLog(msgHash)
|
|
self.deliveryCb(success, dir, comment, msgHash, msg)
|
|
|
|
proc msgChecker(self: RecvMonitor) {.async.} =
|
|
## Continuously checks if a message has been received
|
|
while true:
|
|
await sleepAsync(StoreCheckPeriod)
|
|
|
|
self.endTimeToCheck = getNowInNanosecondTime()
|
|
|
|
var msgHashesInStore = newSeq[WakuMessageHash](0)
|
|
for pubsubTopic, cTopics in self.topicsInterest.pairs:
|
|
let storeResp: StoreQueryResponse = (
|
|
await self.storeClient.queryToAny(
|
|
StoreQueryRequest(
|
|
includeData: false,
|
|
pubsubTopic: some(PubsubTopic(pubsubTopic)),
|
|
contentTopics: cTopics,
|
|
startTime: some(self.startTimeToCheck - DelayExtra.nanos),
|
|
endTime: some(self.endTimeToCheck + DelayExtra.nanos),
|
|
)
|
|
)
|
|
).valueOr:
|
|
error "msgChecker failed to get remote msgHashes",
|
|
pubsubTopic, cTopics, error = $error
|
|
continue
|
|
|
|
msgHashesInStore.add(storeResp.messages.mapIt(it.messageHash))
|
|
|
|
## compare the msgHashes seen from the store vs the ones received directly
|
|
let rxMsgHashes = self.recentReceivedMsgs.mapIt(it.msgHash)
|
|
let missedHashes: seq[WakuMessageHash] =
|
|
msgHashesInStore.filterIt(not rxMsgHashes.contains(it))
|
|
|
|
## Now retrieve the missed WakuMessages
|
|
let missingMsgsRet = await self.getMissingMsgsFromStore(missedHashes)
|
|
if missingMsgsRet.isOk():
|
|
## Give feedback so that the api client can perfom any action with the missed messages
|
|
for msgTuple in missingMsgsRet.get():
|
|
self.performDeliveryFeedback(
|
|
DeliverySuccess.UNSUCCESSFUL, RECEIVING, "Missed message", msgTuple.hash,
|
|
msgTuple.msg,
|
|
)
|
|
else:
|
|
error "failed to retrieve missing messages: ", error = $missingMsgsRet.error
|
|
|
|
## update next check times
|
|
self.startTimeToCheck = self.endTimeToCheck
|
|
|
|
method onSubscribe(
|
|
self: RecvMonitor, pubsubTopic: string, contentTopics: seq[string]
|
|
) {.gcsafe, raises: [].} =
|
|
debug "onSubscribe", pubsubTopic, contentTopics
|
|
self.topicsInterest.withValue(pubsubTopic, contentTopicsOfInterest):
|
|
contentTopicsOfInterest[].add(contentTopics)
|
|
do:
|
|
self.topicsInterest[pubsubTopic] = contentTopics
|
|
|
|
method onUnsubscribe(
|
|
self: RecvMonitor, pubsubTopic: string, contentTopics: seq[string]
|
|
) {.gcsafe, raises: [].} =
|
|
debug "onUnsubscribe", pubsubTopic, contentTopics
|
|
|
|
self.topicsInterest.withValue(pubsubTopic, contentTopicsOfInterest):
|
|
let remainingCTopics =
|
|
contentTopicsOfInterest[].filterIt(not contentTopics.contains(it))
|
|
contentTopicsOfInterest[] = remainingCTopics
|
|
|
|
if remainingCTopics.len == 0:
|
|
self.topicsInterest.del(pubsubTopic)
|
|
do:
|
|
error "onUnsubscribe unsubscribing from wrong topic", pubsubTopic, contentTopics
|
|
|
|
proc new*(
|
|
T: type RecvMonitor,
|
|
storeClient: WakuStoreClient,
|
|
wakuFilterClient: WakuFilterClient,
|
|
): T =
|
|
## The storeClient will help to acquire any possible missed messages
|
|
|
|
let now = getNowInNanosecondTime()
|
|
var recvMonitor = RecvMonitor(storeClient: storeClient, startTimeToCheck: now)
|
|
|
|
if not wakuFilterClient.isNil():
|
|
wakuFilterClient.addSubscrObserver(recvMonitor)
|
|
|
|
let filterPushHandler = proc(
|
|
pubsubTopic: PubsubTopic, message: WakuMessage
|
|
) {.async, closure.} =
|
|
## Captures all the messages recived through filter
|
|
|
|
let msgHash = computeMessageHash(pubSubTopic, message)
|
|
let rxMsg = RecvMessage(msgHash: msgHash, rxTime: message.timestamp)
|
|
recvMonitor.recentReceivedMsgs.add(rxMsg)
|
|
|
|
wakuFilterClient.registerPushHandler(filterPushHandler)
|
|
|
|
return recvMonitor
|
|
|
|
proc loopPruneOldMessages(self: RecvMonitor) {.async.} =
|
|
while true:
|
|
let oldestAllowedTime = getNowInNanosecondTime() - MaxMessageLife.nanos
|
|
self.recentReceivedMsgs.keepItIf(it.rxTime > oldestAllowedTime)
|
|
await sleepAsync(PruneOldMsgsPeriod)
|
|
|
|
proc startRecvMonitor*(self: RecvMonitor) =
|
|
self.msgCheckerHandler = self.msgChecker()
|
|
self.msgPrunerHandler = self.loopPruneOldMessages()
|
|
|
|
proc stopRecvMonitor*(self: RecvMonitor) {.async.} =
|
|
if not self.msgCheckerHandler.isNil():
|
|
await self.msgCheckerHandler.cancelAndWait()
|
|
if not self.msgPrunerHandler.isNil():
|
|
await self.msgPrunerHandler.cancelAndWait()
|
|
|
|
proc setDeliveryCallback*(self: RecvMonitor, deliveryCb: DeliveryFeedbackCallback) =
|
|
self.deliveryCb = deliveryCb
|