Pagination feature/indexing waku messages (#233)

* changes the digest type to MDigest[256] and modifies the computeIndex

* fixes formatting issue

* adds the pagination with its tests

stores and retrieves IndexedWakuMessage


adds the paginate proc


adds the paginate function


fixes some formatting issues


minor


edits indentation and fixes a bug


removes unused imports


minor


fixes indentations and adds a new testcase


adds indexedWakuMessageComparison


adds `==` proc for IndexedWakuMessage


separates the comparison of index and indexed waku messages


adds testcases for the Index comparison and IndexedWakuMessage comparison


WIP


WIP: adds an decoder for Index


removes an unnecessary imports


WIP


adds findIndex() proc


removes the equality check '==' for IndexedWakuMessages


edits the code format and adds the pagination test


edits paginate() proc to work on a copy of the input list


deletes unnecessary echo


adds the boundary calculations for forward and backward pagination


adds test cases for the page boundaries


tests corner cases for the queried cursor and pagesize


minor


adds some comments


adds a proc to extract WakuMessages from a list of IndexedWakuMessages


integrates pagination into the findMessages proc


adds some comments


changes paginate to paginateWithIndex


removes some echos


modifies paginateWithIndex to handle invalid cursors


adds test case for an invalid cursor


WIP: adds a `$` proc for IndexedWakuMessages


adds some debugging message prints 


adds an integration test for handling query with pagination

* fixes a type mismatch issue in the min proc

* replaces boolean direction with their enums and updates contentTopics

* adds the unit test for the sorting of the indexed waku messages

* fixes a flaky test

* fixes a flaky test

* removes index equality check proc

* handles an initial query with an empty cursor

* adds test for the initial query

* adds integration test for pagination

* adds a test for empty message list

* adds comments and fixes an issue

* adds comments

* code cleanup

* removes the content topic validation check

* resolves the errors related to the windows CI tests

* Update waku/protocol/v2/waku_store.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* Update waku/protocol/v2/waku_store.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* Update tests/v2/test_waku_pagination.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* Update waku/protocol/v2/waku_store.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* Update waku/protocol/v2/waku_store.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* Update waku/protocol/v2/waku_store.nim

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>

* changes the output type of findIndex to Option

* Update tests/v2/test_waku_pagination.nim

Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* Update tests/v2/test_waku_pagination.nim

Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* Update tests/v2/test_waku_pagination.nim

Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* Apply suggestions from code review

Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* adds some comments

* fixes an indentation issue

* some code modification for array initialization

* Apply suggestions from code review

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>
Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>

* does some code reorganizations and clean up

* CreateSampleList to createSampleList

* replaces a byte array literal initialization with a for loop

* relocates indexedWakuMessageComparison and indexComparison

* minor

Co-authored-by: Oskar Thorén <ot@oskarthoren.com>
Co-authored-by: Dean Eigenmann <7621705+decanus@users.noreply.github.com>
This commit is contained in:
Sanaz Taheri Boshrooyeh 2020-11-08 20:48:09 -08:00 committed by GitHub
parent 9df31621c4
commit ea5f9993a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 463 additions and 25 deletions

View File

@ -1,11 +1,21 @@
{.used.}
import
std/unittest,
std/[unittest,algorithm,options],
nimcrypto/sha2,
../../waku/node/v2/waku_types,
../../waku/protocol/v2/waku_store,
../test_helpers
proc createSampleList(s: int): seq[IndexedWakuMessage] =
## takes s as input and outputs a sequence with s amount of IndexedWakuMessage
var data {.noinit.}: array[32, byte]
for x in data.mitems: x = 1
for i in 0..<s:
result.add(IndexedWakuMessage(msg: WakuMessage(payload: @[byte i]), index: Index(receivedTime: float64(i), digest: MDigest[256](data: data)) ))
procSuite "pagination":
test "computeIndex: empty contentTopic test":
test "Index computation test":
let
wm = WakuMessage(payload: @[byte 1, 2, 3])
index = wm.computeIndex()
@ -15,10 +25,9 @@ procSuite "pagination":
len(index.digest.data) == 32 # sha2 output length in bytes
index.receivedTime != 0 # the timestamp should be a non-zero value
test "computeIndex: identical WakuMessages test":
let
wm = WakuMessage(payload: @[byte 1, 2, 3], contentTopic: ContentTopic(1))
index1 = wm.computeIndex()
wm1 = WakuMessage(payload: @[byte 1, 2, 3], contentTopic: ContentTopic(1))
index1 = wm1.computeIndex()
wm2 = WakuMessage(payload: @[byte 1, 2, 3], contentTopic: ContentTopic(1))
index2 = wm2.computeIndex()
@ -26,3 +35,183 @@ procSuite "pagination":
# the digests of two identical WakuMessages must be the same
index1.digest == index2.digest
test "Index comparison, IndexedWakuMessage comparison, and Sorting tests":
var data1 {.noinit.}: array[32, byte]
for x in data1.mitems: x = 1
var data2 {.noinit.}: array[32, byte]
for x in data2.mitems: x = 2
var data3 {.noinit.}: array[32, byte]
for x in data3.mitems: x = 3
let
index1 = Index(receivedTime: 1, digest: MDigest[256](data: data1))
index2 = Index(receivedTime: 1, digest: MDigest[256](data: data2))
index3 = Index(receivedTime: 2, digest: MDigest[256](data: data3))
iwm1 = IndexedWakuMessage(index: index1)
iwm2 = IndexedWakuMessage(index: index2)
iwm3 = IndexedWakuMessage(index: index3)
check:
indexComparison(index1, index1) == 0
indexComparison(index1, index2) == -1
indexComparison(index2, index1) == 1
indexComparison(index1, index3) == -1
indexComparison(index3, index1) == 1
check:
indexedWakuMessageComparison(iwm1, iwm1) == 0
indexedWakuMessageComparison(iwm1, iwm2) == -1
indexedWakuMessageComparison(iwm2, iwm1) == 1
indexedWakuMessageComparison(iwm1, iwm3) == -1
indexedWakuMessageComparison(iwm3, iwm1) == 1
var sortingList = @[iwm3, iwm1, iwm2]
sortingList.sort(indexedWakuMessageComparison)
check:
sortingList[0] == iwm1
sortingList[1] == iwm2
sortingList[2] == iwm3
test "Find Index test":
let msgList = createSampleList(10)
check:
msgList.findIndex(msgList[3].index).get() == 3
msgList.findIndex(Index()).isNone == true
test "Forward pagination test":
var
msgList = createSampleList(10)
pagingInfo = PagingInfo(pageSize: 2, cursor: msgList[3].index, direction: PagingDirection.FORWARD)
# test for a normal pagination
var (data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 2
data == msgList[4..5]
newPagingInfo.cursor == msgList[5].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == pagingInfo.pageSize
# test for an initial pagination request with an empty cursor
pagingInfo = PagingInfo(pageSize: 2, direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 2
data == msgList[0..1]
newPagingInfo.cursor == msgList[1].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 2
# test for an empty msgList
pagingInfo = PagingInfo(pageSize: 2, direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(@[], pagingInfo)
check:
data.len == 0
newPagingInfo.pageSize == 0
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.cursor == pagingInfo.cursor
# test for a page size larger than the remaining messages
pagingInfo = PagingInfo(pageSize: 10, cursor: msgList[3].index, direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 6
data == msgList[4..9]
newPagingInfo.cursor == msgList[9].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 6
# test for a page size larger than the maximum allowed page size
pagingInfo = PagingInfo(pageSize: MaxPageSize+1, cursor: msgList[3].index, direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len <= MaxPageSize
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize <= MaxPageSize
# test for a cursor poiting to the end of the message list
pagingInfo = PagingInfo(pageSize: 10, cursor: msgList[9].index, direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 0
newPagingInfo.cursor == msgList[9].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 0
# test for an invalid cursor
pagingInfo = PagingInfo(pageSize: 10, cursor: computeIndex(WakuMessage(payload: @[byte 10])), direction: PagingDirection.FORWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 0
newPagingInfo.cursor == pagingInfo.cursor
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 0
test "Backward pagination test":
var
msgList = createSampleList(10)
pagingInfo = PagingInfo(pageSize: 2, cursor: msgList[3].index, direction: PagingDirection.BACKWARD)
# test for a normal pagination
var (data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data == msgList[1..2]
newPagingInfo.cursor == msgList[1].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == pagingInfo.pageSize
# test for an empty msgList
pagingInfo = PagingInfo(pageSize: 2, direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(@[], pagingInfo)
check:
data.len == 0
newPagingInfo.pageSize == 0
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.cursor == pagingInfo.cursor
# test for an initial pagination request with an empty cursor
pagingInfo = PagingInfo(pageSize: 2, direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 2
data == msgList[8..9]
newPagingInfo.cursor == msgList[8].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 2
# test for a page size larger than the remaining messages
pagingInfo = PagingInfo(pageSize: 5, cursor: msgList[3].index, direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data == msgList[0..2]
newPagingInfo.cursor == msgList[0].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 3
# test for a page size larger than the Maximum allowed page size
pagingInfo = PagingInfo(pageSize: MaxPageSize+1, cursor: msgList[3].index, direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len <= MaxPageSize
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize <= MaxPageSize
# test for a cursor pointing to the begining of the message list
pagingInfo = PagingInfo(pageSize: 5, cursor: msgList[0].index, direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 0
newPagingInfo.cursor == msgList[0].index
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 0
# test for an invalid cursor
pagingInfo = PagingInfo(pageSize: 5, cursor: computeIndex(WakuMessage(payload: @[byte 10])), direction: PagingDirection.BACKWARD)
(data, newPagingInfo) = paginateWithIndex(msgList, pagingInfo)
check:
data.len == 0
newPagingInfo.cursor == pagingInfo.cursor
newPagingInfo.direction == pagingInfo.direction
newPagingInfo.pageSize == 0

View File

@ -15,6 +15,7 @@ import
../../waku/node/v2/waku_types,
../test_helpers, ./utils
procSuite "Waku Store":
asyncTest "handle query":
let
@ -57,6 +58,157 @@ procSuite "Waku Store":
check:
(await completionFut.withTimeout(5.seconds)) == true
asyncTest "handle query with forward pagination":
let
key = PrivateKey.random(ECDSA, rng[]).get()
peer = PeerInfo.init(key)
var
msgList = @[WakuMessage(payload: @[byte 0], contentTopic: ContentTopic(2)),
WakuMessage(payload: @[byte 1],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 2],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 3],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 4],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 5],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 6],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 7],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 8],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 9],contentTopic: ContentTopic(2))]
var dialSwitch = newStandardSwitch()
discard await dialSwitch.start()
var listenSwitch = newStandardSwitch(some(key))
discard await listenSwitch.start()
let
proto = WakuStore.init(dialSwitch, crypto.newRng())
subscription = proto.subscription()
rpc = HistoryQuery(topics: @[ContentTopic(1)], pagingInfo: PagingInfo(pageSize: 2, direction: PagingDirection.FORWARD) )
proto.setPeer(listenSwitch.peerInfo)
var subscriptions = newTable[string, MessageNotificationSubscription]()
subscriptions["test"] = subscription
listenSwitch.mount(proto)
for wakuMsg in msgList:
await subscriptions.notify("foo", wakuMsg)
var completionFut = newFuture[bool]()
proc handler(response: HistoryResponse) {.gcsafe, closure.} =
check:
response.messages.len() == 2
response.pagingInfo.pageSize == 2
response.pagingInfo.direction == PagingDirection.FORWARD
response.pagingInfo.cursor != Index()
completionFut.complete(true)
await proto.query(rpc, handler)
check:
(await completionFut.withTimeout(5.seconds)) == true
asyncTest "handle query with backward pagination":
let
key = PrivateKey.random(ECDSA, rng[]).get()
peer = PeerInfo.init(key)
var
msgList = @[WakuMessage(payload: @[byte 0], contentTopic: ContentTopic(2)),
WakuMessage(payload: @[byte 1],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 2],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 3],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 4],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 5],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 6],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 7],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 8],contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 9],contentTopic: ContentTopic(2))]
var dialSwitch = newStandardSwitch()
discard await dialSwitch.start()
var listenSwitch = newStandardSwitch(some(key))
discard await listenSwitch.start()
let
proto = WakuStore.init(dialSwitch, crypto.newRng())
subscription = proto.subscription()
proto.setPeer(listenSwitch.peerInfo)
var subscriptions = newTable[string, MessageNotificationSubscription]()
subscriptions["test"] = subscription
listenSwitch.mount(proto)
for wakuMsg in msgList:
await subscriptions.notify("foo", wakuMsg)
var completionFut = newFuture[bool]()
proc handler(response: HistoryResponse) {.gcsafe, closure.} =
check:
response.messages.len() == 2
response.pagingInfo.pageSize == 2
response.pagingInfo.direction == PagingDirection.BACKWARD
response.pagingInfo.cursor != Index()
completionFut.complete(true)
let rpc = HistoryQuery(topics: @[ContentTopic(1)], pagingInfo: PagingInfo(pageSize: 2, direction: PagingDirection.BACKWARD) )
await proto.query(rpc, handler)
check:
(await completionFut.withTimeout(5.seconds)) == true
asyncTest "handle queries with no pagination":
let
key = PrivateKey.random(ECDSA, rng[]).get()
peer = PeerInfo.init(key)
var
msgList = @[WakuMessage(payload: @[byte 0], contentTopic: ContentTopic(2)),
WakuMessage(payload: @[byte 1], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 2], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 3], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 4], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 5], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 6], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 7], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 8], contentTopic: ContentTopic(1)),
WakuMessage(payload: @[byte 9], contentTopic: ContentTopic(2))]
var dialSwitch = newStandardSwitch()
discard await dialSwitch.start()
var listenSwitch = newStandardSwitch(some(key))
discard await listenSwitch.start()
let
proto = WakuStore.init(dialSwitch, crypto.newRng())
subscription = proto.subscription()
proto.setPeer(listenSwitch.peerInfo)
var subscriptions = newTable[string, MessageNotificationSubscription]()
subscriptions["test"] = subscription
listenSwitch.mount(proto)
for wakuMsg in msgList:
await subscriptions.notify("foo", wakuMsg)
var completionFut = newFuture[bool]()
proc handler(response: HistoryResponse) {.gcsafe, closure.} =
check:
response.messages.len() == 8
response.pagingInfo == PagingInfo()
completionFut.complete(true)
let rpc = HistoryQuery(topics: @[ContentTopic(1)] )
await proto.query(rpc, handler)
check:
(await completionFut.withTimeout(5.seconds)) == true
test "Index Protobuf encoder/decoder test":
let

View File

@ -12,9 +12,9 @@ import
libp2p/stream/connection,
libp2p/protocols/pubsub/[pubsub, gossipsub],
nimcrypto/sha2
# constants required for pagination -------------------------------------------
const MaxPageSize* = 100 # Maximum number of waku messages in each page
# Common data types -----------------------------------------------------------
type
ContentTopic* = uint32
@ -61,11 +61,11 @@ type
HistoryQuery* = object
topics*: seq[ContentTopic]
pagingInfo*: PagingInfo
pagingInfo*: PagingInfo # used for pagination
HistoryResponse* = object
messages*: seq[WakuMessage]
pagingInfo*: PagingInfo
pagingInfo*: PagingInfo # used for pagination
HistoryRPC* = object
requestId*: string
@ -79,7 +79,8 @@ type
switch*: Switch
rng*: ref BrHmacDrbgContext
peers*: seq[HistoryPeer]
messages*: seq[WakuMessage]
messages*: seq[IndexedWakuMessage]
FilterRequest* = object
contentFilters*: seq[ContentFilter]
@ -145,8 +146,7 @@ type
#multiaddrStrings*: seq[string]
WakuResult*[T] = Result[T, cstring]
# Encoding and decoding -------------------------------------------------------
# Encoding and decoding -------------------------------------------------------
proc init*(T: type WakuMessage, buffer: seq[byte]): ProtoResult[T] =
var msg = WakuMessage()
@ -189,15 +189,13 @@ proc generateRequestId*(rng: ref BrHmacDrbgContext): string =
toHex(bytes)
proc computeIndex*(msg: WakuMessage): Index =
## Takes a WakuMessage and returns its index
## Takes a WakuMessage and returns its Index
var ctx: sha256
ctx.init()
if msg.contentTopic != 0: # checks for non-empty contentTopic
ctx.update(msg.contentTopic.toBytes()) # converts the topic to bytes
ctx.update(msg.contentTopic.toBytes()) # converts the contentTopic to bytes
ctx.update(msg.payload)
let digest = ctx.finish() # computes the hash
ctx.clear()
result.digest = digest
result.receivedTime = epochTime() # gets the unix timestamp
result.receivedTime = epochTime() # gets the unix timestamp

View File

@ -1,7 +1,7 @@
import
std/tables,
std/[tables, sequtils, future, algorithm, options],
bearssl,
chronos, chronicles, metrics, stew/results,
chronos, chronicles, metrics, stew/[results,byteutils],
libp2p/switch,
libp2p/crypto/crypto,
libp2p/protocols/protocol,
@ -173,11 +173,106 @@ proc encode*(rpc: HistoryRPC): ProtoBuffer =
result.write(2, rpc.query.encode())
result.write(3, rpc.response.encode())
proc indexComparison* (x, y: Index): int =
## compares x and y
## returns 0 if they are equal
## returns -1 if x < y
## returns 1 if x > y
let
timecmp = system.cmp(x.receivedTime, y.receivedTime)
digestcm = system.cmp(x.digest.data, y.digest.data)
if timecmp != 0: # timestamp has a higher priority for comparison
return timecmp
return digestcm
proc indexedWakuMessageComparison*(x, y: IndexedWakuMessage): int =
## compares x and y
## returns 0 if they are equal
## returns -1 if x < y
## returns 1 if x > y
result = indexComparison(x.index, y.index)
proc findIndex*(msgList: seq[IndexedWakuMessage], index: Index): Option[int] =
## returns the position of an IndexedWakuMessage in msgList whose index value matches the given index
## returns none if no match is found
for i, indexedWakuMessage in msgList:
if indexedWakuMessage.index == index:
return some(i)
return none(int)
proc paginateWithIndex*(list: seq[IndexedWakuMessage], pinfo: PagingInfo): (seq[IndexedWakuMessage], PagingInfo) =
## takes list, and performs paging based on pinfo
## returns the page i.e, a sequence of IndexedWakuMessage and the new paging info to be used for the next paging request
var
cursor = pinfo.cursor
pageSize = pinfo.pageSize
dir = pinfo.direction
if pageSize == 0: # pageSize being zero indicates that no pagination is required
return (list, pinfo)
if list.len == 0: # no pagination is needed for an empty list
return (list, PagingInfo(pageSize: 0, cursor:pinfo.cursor, direction: pinfo.direction))
var msgList = list # makes a copy of the list
# sorts msgList based on the custom comparison proc indexedWakuMessageComparison
msgList.sort(indexedWakuMessageComparison)
var initQuery = false
if cursor == Index():
initQuery = true # an empty cursor means it is an intial query
case dir
of PagingDirection.FORWARD:
cursor = list[0].index # perform paging from the begining of the list
of PagingDirection.BACKWARD:
cursor = list[list.len - 1].index # perform paging from the end of the list
var foundIndexOption = msgList.findIndex(cursor)
if foundIndexOption.isNone: # the cursor is not valid
return (@[], PagingInfo(pageSize: 0, cursor:pinfo.cursor, direction: pinfo.direction))
var foundIndex = foundIndexOption.get()
var retrievedPageSize, s, e: int
var newCursor: Index # to be returned as part of the new paging info
case dir
of PagingDirection.FORWARD: # forward pagination
let remainingMessages= msgList.len - foundIndex - 1
# the number of queried messages cannot exceed the MaxPageSize and the total remaining messages i.e., msgList.len-foundIndex
retrievedPageSize = min(int(pageSize), MaxPageSize).min(remainingMessages)
if initQuery : foundIndex = foundIndex - 1
s = foundIndex + 1 # non inclusive
e = foundIndex + retrievedPageSize
newCursor = msgList[e].index # the new cursor points to the end of the page
of PagingDirection.BACKWARD: # backward pagination
let remainingMessages=foundIndex
# the number of queried messages cannot exceed the MaxPageSize and the total remaining messages i.e., foundIndex-0
retrievedPageSize = min(int(pageSize), MaxPageSize).min(remainingMessages)
if initQuery : foundIndex = foundIndex + 1
s = foundIndex - retrievedPageSize
e = foundIndex - 1
newCursor = msgList[s].index # the new cursor points to the begining of the page
# retrieve the messages
for i in s..e:
result[0].add(msgList[i])
result[1] = PagingInfo(pageSize : uint64(retrievedPageSize), cursor : newCursor, direction : pinfo.direction)
proc paginateWithoutIndex(list: seq[IndexedWakuMessage], pinfo: PagingInfo): (seq[WakuMessage], PagingInfo) =
## takes list, and perfomrs paging based on pinfo
## returns the page i.e, a sequence of WakuMessage and the new paging info to be used for the next paging request
var (indexedData, updatedPagingInfo) = paginateWithIndex(list,pinfo)
for indexedMsg in indexedData:
result[0].add(indexedMsg.msg)
result[1] = updatedPagingInfo
proc findMessages(w: WakuStore, query: HistoryQuery): HistoryResponse =
result = HistoryResponse(messages: newSeq[WakuMessage]())
for msg in w.messages:
if msg.contentTopic in query.topics:
result.messages.insert(msg)
# data holds IndexedWakuMessage whose topics match the query
var data = w.messages.filterIt(it.msg.contentTopic in query.topics)
# perform pagination
(result.messages, result.pagingInfo)= paginateWithoutIndex(data, query.pagingInfo)
method init*(ws: WakuStore) =
proc handle(conn: Connection, proto: string) {.async, gcsafe, closure.} =
@ -191,7 +286,8 @@ method init*(ws: WakuStore) =
let value = res.value
let response = ws.findMessages(res.value.query)
await conn.writeLp(HistoryRPC(requestId: value.requestId, response: response).encode().buffer)
await conn.writeLp(HistoryRPC(requestId: value.requestId,
response: response).encode().buffer)
ws.handler = handle
ws.codec = WakuStoreCodec
@ -212,7 +308,9 @@ proc subscription*(proto: WakuStore): MessageNotificationSubscription =
## the filter should be used by the component that receives
## new messages.
proc handle(topic: string, msg: WakuMessage) {.async.} =
proto.messages.add(msg)
let index = msg.computeIndex()
proto.messages.add(IndexedWakuMessage(msg: msg, index: index))
MessageNotificationSubscription.init(@[], handle)
@ -227,7 +325,8 @@ proc query*(w: WakuStore, query: HistoryQuery, handler: QueryHandlerFunc) {.asyn
let peer = w.peers[0]
let conn = await w.switch.dial(peer.peerInfo.peerId, peer.peerInfo.addrs, WakuStoreCodec)
await conn.writeLP(HistoryRPC(requestId: generateRequestId(w.rng), query: query).encode().buffer)
await conn.writeLP(HistoryRPC(requestId: generateRequestId(w.rng),
query: query).encode().buffer)
var message = await conn.readLp(64*1024)
let response = HistoryRPC.init(message)