Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: lightpush rest api #2052

Merged
merged 8 commits into from
Sep 22, 2023
3 changes: 3 additions & 0 deletions apps/wakunode2/app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import
../../waku/node/rest/filter/handlers as rest_filter_api,
../../waku/node/rest/store/handlers as rest_store_api,
../../waku/node/rest/health/handlers as rest_health_api,
../../waku/node/rest/lightpush/handlers as rest_lightpush_api,
../../waku/node/jsonrpc/admin/handlers as rpc_admin_api,
../../waku/node/jsonrpc/debug/handlers as rpc_debug_api,
../../waku/node/jsonrpc/filter/handlers as rpc_filter_api,
Expand Down Expand Up @@ -590,6 +591,8 @@ proc startRestServer(app: App, address: ValidIpAddress, port: Port, conf: WakuNo
## Store REST API
installStoreApiHandlers(server.router, app.node)

installLightPushPostPushRequestHandler(server.router, app.node)

server.start()
info "Starting REST HTTP server", url = "http://" & $address & ":" & $port & "/"

Expand Down
3 changes: 2 additions & 1 deletion tests/all_tests_waku.nim
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ import
./wakunode_rest/test_rest_serdes,
./wakunode_rest/test_rest_store,
./wakunode_rest/test_rest_filter,
./wakunode_rest/test_rest_legacy_filter
./wakunode_rest/test_rest_legacy_filter,
./wakunode_rest/test_rest_lightpush

import
./waku_rln_relay/test_waku_rln_relay,
Expand Down
103 changes: 103 additions & 0 deletions tests/wakunode_rest/test_rest_lightpush.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
{.used.}

import
std/sequtils,
stew/byteutils,
stew/shims/net,
testutils/unittests,
presto, presto/client as presto_client,
libp2p/crypto/crypto
import
../../waku/node/message_cache,
../../waku/common/base64,
../../waku/waku_core,
../../waku/waku_node,
../../waku/node/peer_manager,
../../waku/waku_lightpush,
../../waku/node/rest/server,
../../waku/node/rest/client,
../../waku/node/rest/responses,
../../waku/node/rest/lightpush/types,
../../waku/node/rest/lightpush/handlers as lightpush_api,
../../waku/node/rest/lightpush/client as lightpush_api_client,
../../waku/waku_relay,
../testlib/wakucore,
../testlib/wakunode


proc testWakuNode(): WakuNode =
let
privkey = generateSecp256k1Key()
bindIp = ValidIpAddress.init("0.0.0.0")
extIp = ValidIpAddress.init("127.0.0.1")
port = Port(0)

return newTestWakuNode(privkey, bindIp, port, some(extIp), some(port))


type RestLightPushTest = object
serviceNode: WakuNode
pushNode: WakuNode
restServer: RestServerRef
client: RestClientRef


proc init(T: type RestLightPushTest): Future[T] {.async.} =
var testSetup = RestLightPushTest()
testSetup.serviceNode = testWakuNode()
testSetup.pushNode = testWakuNode()

await allFutures(testSetup.serviceNode.start(), testSetup.pushNode.start())

await testSetup.serviceNode.mountRelay()
await testSetup.serviceNode.mountLightPush()
testSetup.pushNode.mountLightPushClient()

testSetup.pushNode.peerManager.addServicePeer(
testSetup.serviceNode.peerInfo.toRemotePeerInfo(),
WakuLightPushCodec)

let restPort = Port(58011)
let restAddress = ValidIpAddress.init("127.0.0.1")
testSetup.restServer = RestServerRef.init(restAddress, restPort).tryGet()

installLightPushPostPushRequestHandler(testSetup.restServer.router, testSetup.pushNode)

testSetup.restServer.start()

testSetup.client = newRestHttpClient(initTAddress(restAddress, restPort))

return testSetup


proc shutdown(self: RestLightPushTest) {.async.} =
await self.restServer.stop()
await self.restServer.closeWait()
await allFutures(self.serviceNode.stop(), self.pushNode.stop())


suite "Waku v2 Rest API - lightpush":
asyncTest "Push message request":
# Given
let restLightPushTest = await RestLightPushTest.init()

restLightPushTest.serviceNode.subscribe(DefaultPubsubTopic)
require:
toSeq(restLightPushTest.serviceNode.wakuRelay.subscribedTopics).len == 1

# When
let message : RelayWakuMessage = fakeWakuMessage(contentTopic = DefaultContentTopic,
payload = toBytes("TEST-1")).toRelayWakuMessage()

let requestBody = PushRequest(pubsubTopic: some(DefaultPubsubTopic),
message: message)
let response = await restLightPushTest.client.sendPushRequest(requestBody)

echo "response", $response

# Then
check:
response.status == 200
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May we have any "400" or "500" test cases as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm working on those. Just not added yet.

$response.contentType == $MIMETYPE_TEXT

await restLightPushTest.shutdown()
49 changes: 49 additions & 0 deletions waku/node/rest/lightpush/client.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}

import
json,
std/sets,
stew/byteutils,
strformat,
chronicles,
json_serialization,
json_serialization/std/options,
presto/[route, client, common]
import
../../../waku_core,
../serdes,
../responses,
./types

export types

logScope:
topics = "waku node rest client v2"

proc encodeBytes*(value: PushRequest,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)

proc decodeBytes*(t: typedesc[string], value: openarray[byte],
contentType: Opt[ContentTypeData]): RestResult[string] =
if MediaType.init($contentType) != MIMETYPE_TEXT:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

var res: string
if len(value) > 0:
res = newString(len(value))
copyMem(addr res[0], unsafeAddr value[0], len(value))
return ok(res)

proc sendPushRequest*(body: PushRequest):
RestResponse[string]
{.rest, endpoint: "/lightpush/v1/message", meth: HttpMethod.MethodPost.}
83 changes: 83 additions & 0 deletions waku/node/rest/lightpush/handlers.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}

import
std/strformat,
std/sequtils,
stew/byteutils,
chronicles,
json_serialization,
json_serialization/std/options,
presto/route,
presto/common

import
../../../waku_core,
../../peer_manager,
../../waku_node,
../../waku/waku_lightpush,
../serdes,
../responses,
./types

export types

logScope:
topics = "waku node rest lightpush api"

const futTimeoutForPushRequestProcessing* = 5.seconds

#### Request handlers

const ROUTE_LIGHTPUSH* = "/lightpush/v1/message"

func decodeRequestBody[T](contentBody: Option[ContentBody]) : Result[T, RestApiResponse] =
if contentBody.isNone():
return err(RestApiResponse.badRequest("Missing content body"))

let reqBodyContentType = MediaType.init($contentBody.get().contentType)
if reqBodyContentType != MIMETYPE_JSON:
return err(RestApiResponse.badRequest("Wrong Content-Type, expected application/json"))

let reqBodyData = contentBody.get().data

let requestResult = decodeFromJsonBytes(T, reqBodyData)
if requestResult.isErr():
return err(RestApiResponse.badRequest("Invalid content body, could not decode. " &
$requestResult.error))

return ok(requestResult.get())

proc installLightPushPostPushRequestHandler*(router: var RestRouter,
node: WakuNode) =

router.api(MethodPost, ROUTE_LIGHTPUSH) do (contentBody: Option[ContentBody]) -> RestApiResponse:
## Send a request to push a waku message
debug "post", ROUTE_LIGHTPUSH, contentBody

let decodedBody = decodeRequestBody[PushRequest](contentBody)

if decodedBody.isErr():
return decodedBody.error()

let req: PushRequest = decodedBody.value()
let msg = req.message.toWakuMessage()

if msg.isErr():
return RestApiResponse.badRequest("Invalid message: {msg.error}")

let peerOpt = node.peerManager.selectPeer(WakuLightPushCodec)
if peerOpt.isNone():
return RestApiResponse.serviceUnavailable("No suitable remote lightpush peers")

let subFut = node.lightpushPublish(req.pubsubTopic,
msg.value(),
peerOpt.get())

if not await subFut.withTimeout(futTimeoutForPushRequestProcessing):
error "Failed to request a message push due to timeout!"
return RestApiResponse.serviceUnavailable("Push request timed out")

return RestApiResponse.ok()
84 changes: 84 additions & 0 deletions waku/node/rest/lightpush/openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
openapi: 3.0.3
info:
title: Waku V2 node REST API
version: 1.0.0
contact:
name: VAC Team
url: https://forum.vac.dev/

tags:
- name: lightpush
description: Lightpush REST API for WakuV2 node

paths:
/lightpush/v1/message:
post:
summary: Request a message relay from a LightPush service provider
description: Push a message to be relayed on a PubSub topic.
operationId: postMessagesToPubsubTopic
tags:
- lightpush
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/PushRequest'
responses:
'200':
description: OK
content:
text/plain:
schema:
type: string
'400':
description: Bad request.
content:
text/plain:
schema:
type: string
'500':
description: Internal server error
content:
text/plain:
schema:
type: string
'503':
description: Service not available
content:
text/plain:
schema:
type: string

components:
schemas:
PubsubTopic:
type: string

ContentTopic:
type: string

WakuMessage:
type: object
properties:
payload:
type: string
format: byte
contentTopic:
$ref: '#/components/schemas/ContentTopic'
version:
type: number
timestamp:
type: number
required:
- payload
- contentTopic

PushRequest:
type: object
properties:
pusbsubTopic:
$ref: '#/components/schemas/PubsubTopic'
message:
$ref: '#/components/schemas/WakuMessage'
required:
- message
Loading