2023-04-06 11:43:19 +02:00
|
|
|
when (NimMajor, NimMinor) < (1, 4):
|
|
|
|
{.push raises: [Defect].}
|
|
|
|
else:
|
|
|
|
{.push raises: [].}
|
|
|
|
|
2024-03-16 00:08:47 +01:00
|
|
|
import std/strformat, stew/results, chronicles, uri, json_serialization, presto/route
|
2023-04-06 11:43:19 +02:00
|
|
|
import
|
2023-04-19 13:29:23 +02:00
|
|
|
../../../waku_core,
|
2023-04-18 15:22:10 +02:00
|
|
|
../../../waku_store/common,
|
2024-01-31 17:43:59 +01:00
|
|
|
../../../waku_store/self_req_handler,
|
2023-09-22 09:36:46 -04:00
|
|
|
../../../waku_node,
|
|
|
|
../../../node/peer_manager,
|
2023-12-19 15:10:27 +01:00
|
|
|
../../../common/paging,
|
2023-10-27 15:43:54 -04:00
|
|
|
../../handlers,
|
2023-04-06 11:43:19 +02:00
|
|
|
../responses,
|
|
|
|
../serdes,
|
|
|
|
./types
|
|
|
|
|
|
|
|
export types
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "waku node rest store_api"
|
|
|
|
|
|
|
|
const futTimeout* = 5.seconds # Max time to wait for futures
|
|
|
|
|
2024-03-16 00:08:47 +01:00
|
|
|
const NoPeerNoDiscError* =
|
|
|
|
RestApiResponse.preconditionFailed("No suitable service peer & no discovery method")
|
2023-10-27 15:43:54 -04:00
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
# Queries the store-node with the query parameters and
|
|
|
|
# returns a RestApiResponse that is sent back to the api client.
|
2024-04-25 09:09:52 -04:00
|
|
|
proc performStoreQuery(
|
|
|
|
selfNode: WakuNode, storeQuery: StoreQueryRequest, storePeer: RemotePeerInfo
|
2024-03-16 00:08:47 +01:00
|
|
|
): Future[RestApiResponse] {.async.} =
|
2024-04-25 09:09:52 -04:00
|
|
|
let queryFut = selfNode.query(storeQuery, storePeer)
|
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
if not await queryFut.withTimeout(futTimeout):
|
|
|
|
const msg = "No history response received (timeout)"
|
|
|
|
error msg
|
|
|
|
return RestApiResponse.internalServerError(msg)
|
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
let futRes = queryFut.read()
|
|
|
|
|
|
|
|
if futRes.isErr():
|
|
|
|
const msg = "Error occurred in queryFut.read()"
|
|
|
|
error msg, error = futRes.error
|
|
|
|
return RestApiResponse.internalServerError(fmt("{msg} [{futRes.error}]"))
|
|
|
|
|
|
|
|
let res = futRes.get()
|
|
|
|
|
|
|
|
if res.statusCode == uint32(ErrorCode.TOO_MANY_REQUESTS):
|
|
|
|
debug "Request rate limit reached on peer ", storePeer
|
|
|
|
return RestApiResponse.tooManyRequests("Request rate limit reached")
|
|
|
|
|
|
|
|
let resp = RestApiResponse.jsonResponse(res, status = Http200).valueOr:
|
2023-04-06 11:43:19 +02:00
|
|
|
const msg = "Error building the json respose"
|
2024-04-25 09:09:52 -04:00
|
|
|
error msg, error = error
|
|
|
|
return RestApiResponse.internalServerError(fmt("{msg} [{error}]"))
|
2023-04-06 11:43:19 +02:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
return resp
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
# Converts a string time representation into an Option[Timestamp].
|
|
|
|
# Only positive time is considered a valid Timestamp in the request
|
2024-03-16 00:08:47 +01:00
|
|
|
proc parseTime(input: Option[string]): Result[Option[Timestamp], string] =
|
2023-04-06 11:43:19 +02:00
|
|
|
if input.isSome() and input.get() != "":
|
|
|
|
try:
|
|
|
|
let time = parseInt(input.get())
|
|
|
|
if time > 0:
|
|
|
|
return ok(some(Timestamp(time)))
|
|
|
|
except ValueError:
|
2024-04-25 09:09:52 -04:00
|
|
|
return err("time parsing error: " & getCurrentExceptionMsg())
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
return ok(none(Timestamp))
|
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
proc parseIncludeData(input: Option[string]): Result[bool, string] =
|
|
|
|
var includeData = false
|
|
|
|
if input.isSome() and input.get() != "":
|
|
|
|
try:
|
|
|
|
includeData = parseBool(input.get())
|
|
|
|
except ValueError:
|
|
|
|
return err("include data parsing error: " & getCurrentExceptionMsg())
|
2023-04-06 11:43:19 +02:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
return ok(includeData)
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
# Creates a HistoryQuery from the given params
|
2024-04-25 09:09:52 -04:00
|
|
|
proc createStoreQuery(
|
|
|
|
includeData: Option[string],
|
2024-03-16 00:08:47 +01:00
|
|
|
pubsubTopic: Option[string],
|
|
|
|
contentTopics: Option[string],
|
|
|
|
startTime: Option[string],
|
|
|
|
endTime: Option[string],
|
2024-04-25 09:09:52 -04:00
|
|
|
hashes: Option[string],
|
|
|
|
cursor: Option[string],
|
2024-03-16 00:08:47 +01:00
|
|
|
direction: Option[string],
|
2024-04-25 09:09:52 -04:00
|
|
|
pageSize: Option[string],
|
|
|
|
): Result[StoreQueryRequest, string] =
|
|
|
|
var parsedIncludeData = ?parseIncludeData(includeData)
|
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
# Parse pubsubTopic parameter
|
|
|
|
var parsedPubsubTopic = none(string)
|
|
|
|
if pubsubTopic.isSome():
|
|
|
|
let decodedPubsubTopic = decodeUrl(pubsubTopic.get())
|
|
|
|
if decodedPubsubTopic != "":
|
|
|
|
parsedPubsubTopic = some(decodedPubsubTopic)
|
|
|
|
|
|
|
|
# Parse the content topics
|
|
|
|
var parsedContentTopics = newSeq[ContentTopic](0)
|
|
|
|
if contentTopics.isSome():
|
|
|
|
let ctList = decodeUrl(contentTopics.get())
|
|
|
|
if ctList != "":
|
|
|
|
for ct in ctList.split(','):
|
|
|
|
parsedContentTopics.add(ct)
|
|
|
|
|
|
|
|
# Parse start time
|
2024-03-16 00:08:47 +01:00
|
|
|
let parsedStartTime = ?parseTime(startTime)
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
# Parse end time
|
2024-03-16 00:08:47 +01:00
|
|
|
let parsedEndTime = ?parseTime(endTime)
|
2023-04-06 11:43:19 +02:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
var parsedHashes = ?parseHashes(hashes)
|
|
|
|
|
|
|
|
# Parse cursor information
|
|
|
|
let parsedCursor = ?parseHash(cursor)
|
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
# Parse ascending field
|
2023-12-19 15:10:27 +01:00
|
|
|
var parsedDirection = default()
|
|
|
|
if direction.isSome() and direction.get() != "":
|
|
|
|
parsedDirection = direction.get().into()
|
2023-04-06 11:43:19 +02:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
# Parse page size field
|
|
|
|
var parsedPagedSize = none(uint64)
|
|
|
|
if pageSize.isSome() and pageSize.get() != "":
|
|
|
|
try:
|
|
|
|
parsedPagedSize = some(uint64(parseInt(pageSize.get())))
|
|
|
|
except CatchableError:
|
|
|
|
return err("page size parsing error: " & getCurrentExceptionMsg())
|
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
return ok(
|
2024-04-25 09:09:52 -04:00
|
|
|
StoreQueryRequest(
|
|
|
|
includeData: parsedIncludeData,
|
2024-03-16 00:08:47 +01:00
|
|
|
pubsubTopic: parsedPubsubTopic,
|
|
|
|
contentTopics: parsedContentTopics,
|
|
|
|
startTime: parsedStartTime,
|
|
|
|
endTime: parsedEndTime,
|
2024-04-25 09:09:52 -04:00
|
|
|
messageHashes: parsedHashes,
|
|
|
|
paginationCursor: parsedCursor,
|
|
|
|
paginationForward: parsedDirection,
|
|
|
|
paginationLimit: parsedPagedSize,
|
2024-03-16 00:08:47 +01:00
|
|
|
)
|
|
|
|
)
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
# Simple type conversion. The "Option[Result[string, cstring]]"
|
|
|
|
# type is used by the nim-presto library.
|
|
|
|
proc toOpt(self: Option[Result[string, cstring]]): Option[string] =
|
|
|
|
if not self.isSome() or self.get().value == "":
|
|
|
|
return none(string)
|
|
|
|
if self.isSome() and self.get().value != "":
|
|
|
|
return some(self.get().value)
|
|
|
|
|
2024-03-16 00:08:47 +01:00
|
|
|
proc retrieveMsgsFromSelfNode(
|
2024-04-25 09:09:52 -04:00
|
|
|
self: WakuNode, storeQuery: StoreQueryRequest
|
2024-03-16 00:08:47 +01:00
|
|
|
): Future[RestApiResponse] {.async.} =
|
2024-01-31 17:43:59 +01:00
|
|
|
## Performs a "store" request to the local node (self node.)
|
|
|
|
## Notice that this doesn't follow the regular store libp2p channel because a node
|
|
|
|
## it is not allowed to libp2p-dial a node to itself, by default.
|
|
|
|
##
|
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
let storeResp = (await self.wakuStore.handleSelfStoreRequest(storeQuery)).valueOr:
|
2024-01-31 17:43:59 +01:00
|
|
|
return RestApiResponse.internalServerError($error)
|
|
|
|
|
2024-03-16 00:08:47 +01:00
|
|
|
let resp = RestApiResponse.jsonResponse(storeResp, status = Http200).valueOr:
|
2024-01-31 17:43:59 +01:00
|
|
|
const msg = "Error building the json respose"
|
2024-03-16 00:08:47 +01:00
|
|
|
error msg, error = error
|
2024-01-31 17:43:59 +01:00
|
|
|
return RestApiResponse.internalServerError(fmt("{msg} [{error}]"))
|
|
|
|
|
|
|
|
return resp
|
|
|
|
|
2023-04-06 11:43:19 +02:00
|
|
|
# Subscribes the rest handler to attend "/store/v1/messages" requests
|
2023-10-27 15:43:54 -04:00
|
|
|
proc installStoreApiHandlers*(
|
2024-03-16 00:08:47 +01:00
|
|
|
router: var RestRouter,
|
|
|
|
node: WakuNode,
|
|
|
|
discHandler: Option[DiscoveryHandler] = none(DiscoveryHandler),
|
|
|
|
) =
|
2023-04-06 11:43:19 +02:00
|
|
|
# Handles the store-query request according to the passed parameters
|
2024-04-25 09:09:52 -04:00
|
|
|
router.api(MethodGet, "/store/v3/messages") do(
|
2024-03-16 00:08:47 +01:00
|
|
|
peerAddr: Option[string],
|
2024-04-25 09:09:52 -04:00
|
|
|
includeData: Option[string],
|
2024-03-16 00:08:47 +01:00
|
|
|
pubsubTopic: Option[string],
|
|
|
|
contentTopics: Option[string],
|
|
|
|
startTime: Option[string],
|
|
|
|
endTime: Option[string],
|
2024-04-25 09:09:52 -04:00
|
|
|
hashes: Option[string],
|
|
|
|
cursor: Option[string],
|
|
|
|
ascending: Option[string],
|
|
|
|
pageSize: Option[string]
|
2024-03-16 00:08:47 +01:00
|
|
|
) -> RestApiResponse:
|
2024-04-25 09:09:52 -04:00
|
|
|
let peer = peerAddr.toOpt()
|
|
|
|
|
|
|
|
debug "REST-GET /store/v3/messages ", peer_addr = $peer
|
2023-04-06 11:43:19 +02:00
|
|
|
|
|
|
|
# All the GET parameters are URL-encoded (https://en.wikipedia.org/wiki/URL_encoding)
|
|
|
|
# Example:
|
|
|
|
# /store/v1/messages?peerAddr=%2Fip4%2F127.0.0.1%2Ftcp%2F60001%2Fp2p%2F16Uiu2HAmVFXtAfSj4EiR7mL2KvL4EE2wztuQgUSBoj2Jx2KeXFLN\&pubsubTopic=my-waku-topic
|
|
|
|
|
|
|
|
# Parse the rest of the parameters and create a HistoryQuery
|
2024-04-25 09:09:52 -04:00
|
|
|
let storeQuery = createStoreQuery(
|
|
|
|
includeData.toOpt(),
|
2024-03-16 00:08:47 +01:00
|
|
|
pubsubTopic.toOpt(),
|
|
|
|
contentTopics.toOpt(),
|
|
|
|
startTime.toOpt(),
|
|
|
|
endTime.toOpt(),
|
2024-04-25 09:09:52 -04:00
|
|
|
hashes.toOpt(),
|
|
|
|
cursor.toOpt(),
|
2024-03-16 00:08:47 +01:00
|
|
|
ascending.toOpt(),
|
2024-04-25 09:09:52 -04:00
|
|
|
pageSize.toOpt(),
|
|
|
|
).valueOr:
|
|
|
|
return RestApiResponse.badRequest(error)
|
2023-04-06 11:43:19 +02:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
if peer.isNone() and not node.wakuStore.isNil():
|
2024-01-31 17:43:59 +01:00
|
|
|
## The user didn't specify a peer address and self-node is configured as a store node.
|
|
|
|
## In this case we assume that the user is willing to retrieve the messages stored by
|
|
|
|
## the local/self store node.
|
2024-04-25 09:09:52 -04:00
|
|
|
return await node.retrieveMsgsFromSelfNode(storeQuery)
|
2024-01-31 17:43:59 +01:00
|
|
|
|
|
|
|
# Parse the peer address parameter
|
2024-04-25 09:09:52 -04:00
|
|
|
let parsedPeerAddr = parseUrlPeerAddr(peer).valueOr:
|
2024-01-31 17:43:59 +01:00
|
|
|
return RestApiResponse.badRequest(error)
|
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
let peerInfo = parsedPeerAddr.valueOr:
|
2024-01-31 17:43:59 +01:00
|
|
|
node.peerManager.selectPeer(WakuStoreCodec).valueOr:
|
|
|
|
let handler = discHandler.valueOr:
|
|
|
|
return NoPeerNoDiscError
|
|
|
|
|
|
|
|
let peerOp = (await handler()).valueOr:
|
|
|
|
return RestApiResponse.internalServerError($error)
|
|
|
|
|
|
|
|
peerOp.valueOr:
|
|
|
|
return RestApiResponse.preconditionFailed(
|
2024-03-16 00:08:47 +01:00
|
|
|
"No suitable service peer & none discovered"
|
|
|
|
)
|
2024-01-31 17:43:59 +01:00
|
|
|
|
2024-04-25 09:09:52 -04:00
|
|
|
return await node.performStoreQuery(storeQuery, peerInfo)
|