2022-11-22 18:40:24 +00:00
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
else:
|
|
|
|
{.push raises: [].}
|
|
|
|
|
|
|
|
|
|
|
|
import
|
2023-01-26 09:19:58 +00:00
|
|
|
std/[tables, times, sequtils, options, algorithm],
|
2022-11-22 18:40:24 +00:00
|
|
|
stew/results,
|
|
|
|
chronicles,
|
|
|
|
chronos,
|
|
|
|
metrics
|
|
|
|
import
|
2023-04-19 11:29:23 +00:00
|
|
|
../waku_core,
|
2022-11-22 18:40:24 +00:00
|
|
|
./common,
|
|
|
|
./archive_metrics,
|
|
|
|
./retention_policy,
|
|
|
|
./driver
|
|
|
|
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "waku archive"
|
|
|
|
|
|
|
|
const
|
|
|
|
DefaultPageSize*: uint = 20
|
|
|
|
|
|
|
|
MaxPageSize*: uint = 100
|
|
|
|
|
|
|
|
|
|
|
|
## Message validation
|
|
|
|
|
|
|
|
type
|
|
|
|
MessageValidator* = ref object of RootObj
|
|
|
|
|
|
|
|
ValidationResult* = Result[void, string]
|
|
|
|
|
|
|
|
method validate*(validator: MessageValidator, msg: WakuMessage): ValidationResult {.base.} = discard
|
|
|
|
|
|
|
|
|
|
|
|
# Default message validator
|
|
|
|
|
|
|
|
const MaxMessageTimestampVariance* = getNanoSecondTime(20) # 20 seconds maximum allowable sender timestamp "drift"
|
|
|
|
|
|
|
|
|
|
|
|
type DefaultMessageValidator* = ref object of MessageValidator
|
|
|
|
|
|
|
|
method validate*(validator: DefaultMessageValidator, msg: WakuMessage): ValidationResult =
|
|
|
|
if msg.timestamp == 0:
|
|
|
|
return ok()
|
|
|
|
|
|
|
|
let
|
|
|
|
now = getNanosecondTime(getTime().toUnixFloat())
|
|
|
|
lowerBound = now - MaxMessageTimestampVariance
|
|
|
|
upperBound = now + MaxMessageTimestampVariance
|
|
|
|
|
|
|
|
if msg.timestamp < lowerBound:
|
|
|
|
return err(invalidMessageOld)
|
|
|
|
|
|
|
|
if upperBound < msg.timestamp:
|
|
|
|
return err(invalidMessageFuture)
|
|
|
|
|
|
|
|
ok()
|
|
|
|
|
|
|
|
|
|
|
|
## Archive
|
|
|
|
|
|
|
|
type
|
|
|
|
WakuArchive* = ref object
|
2022-11-23 09:08:00 +00:00
|
|
|
driver*: ArchiveDriver # TODO: Make this field private. Remove asterisk
|
2022-11-22 18:40:24 +00:00
|
|
|
validator: MessageValidator
|
|
|
|
retentionPolicy: RetentionPolicy
|
|
|
|
|
|
|
|
proc new*(T: type WakuArchive,
|
|
|
|
driver: ArchiveDriver,
|
|
|
|
validator = none(MessageValidator),
|
|
|
|
retentionPolicy = none(RetentionPolicy)): T =
|
|
|
|
WakuArchive(
|
|
|
|
driver: driver,
|
|
|
|
validator: validator.get(nil),
|
|
|
|
retentionPolicy: retentionPolicy.get(nil)
|
|
|
|
)
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
proc handleMessage*(w: WakuArchive,
|
|
|
|
pubsubTopic: PubsubTopic,
|
|
|
|
msg: WakuMessage) {.async.} =
|
2022-11-22 18:40:24 +00:00
|
|
|
if msg.ephemeral:
|
|
|
|
# Ephemeral message, do not store
|
|
|
|
return
|
|
|
|
|
|
|
|
if not w.validator.isNil():
|
|
|
|
let validationRes = w.validator.validate(msg)
|
|
|
|
if validationRes.isErr():
|
2022-11-25 01:06:42 +00:00
|
|
|
waku_archive_errors.inc(labelValues = [validationRes.error])
|
2022-11-22 18:40:24 +00:00
|
|
|
return
|
|
|
|
|
2022-11-25 22:20:24 +00:00
|
|
|
let insertStartTime = getTime().toUnixFloat()
|
|
|
|
|
|
|
|
block:
|
2022-11-22 18:40:24 +00:00
|
|
|
let
|
|
|
|
msgDigest = computeDigest(msg)
|
|
|
|
msgReceivedTime = if msg.timestamp > 0: msg.timestamp
|
|
|
|
else: getNanosecondTime(getTime().toUnixFloat())
|
|
|
|
|
|
|
|
trace "handling message", pubsubTopic=pubsubTopic, contentTopic=msg.contentTopic, timestamp=msg.timestamp, digest=msgDigest
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
let putRes = await w.driver.put(pubsubTopic, msg, msgDigest, msgReceivedTime)
|
2022-11-22 18:40:24 +00:00
|
|
|
if putRes.isErr():
|
|
|
|
error "failed to insert message", err=putRes.error
|
|
|
|
waku_archive_errors.inc(labelValues = [insertFailure])
|
|
|
|
|
2022-11-25 22:20:24 +00:00
|
|
|
let insertDuration = getTime().toUnixFloat() - insertStartTime
|
|
|
|
waku_archive_insert_duration_seconds.observe(insertDuration)
|
|
|
|
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
proc findMessages*(w: WakuArchive, query: ArchiveQuery): Future[ArchiveResult] {.async, gcsafe.} =
|
2022-11-22 18:40:24 +00:00
|
|
|
## Search the archive to return a single page of messages matching the query criteria
|
|
|
|
let
|
|
|
|
qContentTopics = query.contentTopics
|
|
|
|
qPubSubTopic = query.pubsubTopic
|
|
|
|
qCursor = query.cursor
|
|
|
|
qStartTime = query.startTime
|
|
|
|
qEndTime = query.endTime
|
|
|
|
qMaxPageSize = if query.pageSize <= 0: DefaultPageSize
|
|
|
|
else: min(query.pageSize, MaxPageSize)
|
|
|
|
qAscendingOrder = query.ascending
|
|
|
|
|
2023-01-11 11:19:59 +00:00
|
|
|
if qContentTopics.len > 10:
|
|
|
|
return err(ArchiveError.invalidQuery("too many content topics"))
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2022-11-25 22:20:24 +00:00
|
|
|
let queryStartTime = getTime().toUnixFloat()
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
let queryRes = await w.driver.getMessages(
|
2022-11-22 18:40:24 +00:00
|
|
|
contentTopic = qContentTopics,
|
|
|
|
pubsubTopic = qPubSubTopic,
|
|
|
|
cursor = qCursor,
|
|
|
|
startTime = qStartTime,
|
|
|
|
endTime = qEndTime,
|
|
|
|
maxPageSize = qMaxPageSize + 1,
|
|
|
|
ascendingOrder = qAscendingOrder
|
|
|
|
)
|
|
|
|
|
2022-11-25 22:20:24 +00:00
|
|
|
let queryDuration = getTime().toUnixFloat() - queryStartTime
|
|
|
|
waku_archive_query_duration_seconds.observe(queryDuration)
|
|
|
|
|
2022-11-22 18:40:24 +00:00
|
|
|
# Build response
|
|
|
|
if queryRes.isErr():
|
|
|
|
return err(ArchiveError(kind: ArchiveErrorKind.DRIVER_ERROR, cause: queryRes.error))
|
|
|
|
|
|
|
|
let rows = queryRes.get()
|
2023-01-26 09:19:58 +00:00
|
|
|
var messages = newSeq[WakuMessage]()
|
|
|
|
var cursor = none(ArchiveCursor)
|
|
|
|
if rows.len == 0:
|
|
|
|
return ok(ArchiveResponse(messages: messages, cursor: cursor))
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2023-01-26 09:19:58 +00:00
|
|
|
## Messages
|
|
|
|
let pageSize = min(rows.len, int(qMaxPageSize))
|
|
|
|
messages = rows[0..<pageSize].mapIt(it[1])
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2023-01-26 09:19:58 +00:00
|
|
|
## Cursor
|
2022-11-22 18:40:24 +00:00
|
|
|
if rows.len > int(qMaxPageSize):
|
|
|
|
## Build last message cursor
|
|
|
|
## The cursor is built from the last message INCLUDED in the response
|
|
|
|
## (i.e. the second last message in the rows list)
|
|
|
|
let (pubsubTopic, message, digest, storeTimestamp) = rows[^2]
|
|
|
|
|
|
|
|
# TODO: Improve coherence of MessageDigest type
|
2023-01-26 09:19:58 +00:00
|
|
|
let messageDigest = block:
|
|
|
|
var data: array[32, byte]
|
|
|
|
for i in 0..<min(digest.len, 32):
|
|
|
|
data[i] = digest[i]
|
|
|
|
|
|
|
|
MessageDigest(data: data)
|
2022-11-22 18:40:24 +00:00
|
|
|
|
|
|
|
cursor = some(ArchiveCursor(
|
|
|
|
pubsubTopic: pubsubTopic,
|
|
|
|
senderTime: message.timestamp,
|
|
|
|
storeTime: storeTimestamp,
|
2023-01-26 09:19:58 +00:00
|
|
|
digest: messageDigest
|
2022-11-22 18:40:24 +00:00
|
|
|
))
|
|
|
|
|
2023-01-26 09:19:58 +00:00
|
|
|
# All messages MUST be returned in chronological order
|
|
|
|
if not qAscendingOrder:
|
|
|
|
reverse(messages)
|
2022-11-22 18:40:24 +00:00
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
return ok(ArchiveResponse(messages: messages, cursor: cursor))
|
2022-11-22 18:40:24 +00:00
|
|
|
|
|
|
|
# Retention policy
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
proc executeMessageRetentionPolicy*(w: WakuArchive) {.async.} =
|
2022-11-22 18:40:24 +00:00
|
|
|
if w.retentionPolicy.isNil():
|
|
|
|
return
|
|
|
|
|
|
|
|
if w.driver.isNil():
|
|
|
|
return
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
let retPolicyRes = await w.retentionPolicy.execute(w.driver)
|
2022-11-22 18:40:24 +00:00
|
|
|
if retPolicyRes.isErr():
|
|
|
|
waku_archive_errors.inc(labelValues = [retPolicyFailure])
|
|
|
|
error "failed execution of retention policy", error=retPolicyRes.error
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
proc reportStoredMessagesMetric*(w: WakuArchive) {.async.} =
|
2022-11-22 18:40:24 +00:00
|
|
|
if w.driver.isNil():
|
|
|
|
return
|
|
|
|
|
2023-05-25 15:34:34 +00:00
|
|
|
let resCount = await w.driver.getMessagesCount()
|
2022-11-22 18:40:24 +00:00
|
|
|
if resCount.isErr():
|
|
|
|
error "failed to get messages count", error=resCount.error
|
|
|
|
return
|
|
|
|
|
|
|
|
waku_archive_messages.set(resCount.value, labelValues = ["stored"])
|