2021-03-18 16:40:47 +00:00
package store
import (
"bytes"
"context"
"encoding/hex"
"errors"
2021-03-22 16:45:13 +00:00
"fmt"
2021-06-10 13:00:06 +00:00
"math"
2021-03-18 16:40:47 +00:00
"sort"
2022-03-02 16:02:13 +00:00
"strings"
2021-11-23 15:03:12 +00:00
"sync"
2021-11-05 14:27:30 +00:00
"time"
2021-03-18 16:40:47 +00:00
"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/peer"
libp2pProtocol "github.com/libp2p/go-libp2p-core/protocol"
2021-04-07 21:16:29 +00:00
"github.com/libp2p/go-msgio/protoio"
2022-01-18 18:17:06 +00:00
"go.uber.org/zap"
2021-03-22 16:45:13 +00:00
2021-10-25 19:41:08 +00:00
"github.com/status-im/go-waku/waku/persistence"
2021-06-28 13:20:23 +00:00
"github.com/status-im/go-waku/waku/v2/metrics"
2021-03-18 16:40:47 +00:00
"github.com/status-im/go-waku/waku/v2/protocol"
2021-04-22 00:09:37 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/pb"
2021-12-06 10:49:13 +00:00
"github.com/status-im/go-waku/waku/v2/protocol/swap"
2021-06-16 10:14:22 +00:00
"github.com/status-im/go-waku/waku/v2/utils"
2021-03-18 16:40:47 +00:00
)
2022-02-23 15:06:47 +00:00
// StoreID_v20beta4 is the current Waku Store protocol identifier
const StoreID_v20beta4 = libp2pProtocol . ID ( "/vac/waku/store/2.0.0-beta4" )
2021-11-05 20:09:48 +00:00
// MaxPageSize is the maximum number of waku messages to return per page
const MaxPageSize = 100
2021-03-18 16:40:47 +00:00
2021-04-15 02:17:53 +00:00
var (
2021-06-10 13:00:06 +00:00
ErrNoPeersAvailable = errors . New ( "no suitable remote peers" )
ErrInvalidId = errors . New ( "invalid request id" )
ErrFailedToResumeHistory = errors . New ( "failed to resume the history" )
ErrFailedQuery = errors . New ( "failed to resolve the query" )
2021-04-15 02:17:53 +00:00
)
2021-03-18 16:40:47 +00:00
func minOf ( vars ... int ) int {
min := vars [ 0 ]
for _ , i := range vars {
if min > i {
min = i
}
}
return min
}
2021-04-22 00:09:37 +00:00
func paginateWithIndex ( list [ ] IndexedWakuMessage , pinfo * pb . PagingInfo ) ( resMessages [ ] IndexedWakuMessage , resPagingInfo * pb . PagingInfo ) {
2021-10-25 19:41:08 +00:00
if pinfo == nil {
pinfo = new ( pb . PagingInfo )
}
2021-03-18 16:40:47 +00:00
// 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
cursor := pinfo . Cursor
pageSize := pinfo . PageSize
dir := pinfo . Direction
if len ( list ) == 0 { // no pagination is needed for an empty list
2021-04-22 00:09:37 +00:00
return list , & pb . PagingInfo { PageSize : 0 , Cursor : pinfo . Cursor , Direction : pinfo . Direction }
2021-03-18 16:40:47 +00:00
}
2022-01-18 18:17:06 +00:00
if pageSize == 0 {
pageSize = MaxPageSize
}
2021-03-18 16:40:47 +00:00
msgList := make ( [ ] IndexedWakuMessage , len ( list ) )
_ = copy ( msgList , list ) // makes a copy of the list
sort . Slice ( msgList , func ( i , j int ) bool { // sorts msgList based on the custom comparison proc indexedWakuMessageComparison
return indexedWakuMessageComparison ( msgList [ i ] , msgList [ j ] ) == - 1
} )
initQuery := false
if cursor == nil {
initQuery = true // an empty cursor means it is an initial query
switch dir {
2021-04-22 00:09:37 +00:00
case pb . PagingInfo_FORWARD :
2021-10-09 18:18:53 +00:00
cursor = list [ 0 ] . index // perform paging from the beginning of the list
2021-04-22 00:09:37 +00:00
case pb . PagingInfo_BACKWARD :
2021-03-18 16:40:47 +00:00
cursor = list [ len ( list ) - 1 ] . index // perform paging from the end of the list
}
}
2021-04-04 18:42:08 +00:00
foundIndex := findIndex ( msgList , cursor )
2021-03-18 16:40:47 +00:00
if foundIndex == - 1 { // the cursor is not valid
2021-04-22 00:09:37 +00:00
return nil , & pb . PagingInfo { PageSize : 0 , Cursor : pinfo . Cursor , Direction : pinfo . Direction }
2021-03-18 16:40:47 +00:00
}
var retrievedPageSize , s , e int
2021-04-22 00:09:37 +00:00
var newCursor * pb . Index // to be returned as part of the new paging info
2021-03-18 16:40:47 +00:00
switch dir {
2021-04-22 00:09:37 +00:00
case pb . PagingInfo_FORWARD : // forward pagination
2021-03-18 16:40:47 +00:00
remainingMessages := len ( msgList ) - foundIndex - 1
if initQuery {
2021-04-12 17:59:09 +00:00
remainingMessages = remainingMessages + 1
2021-03-18 16:40:47 +00:00
foundIndex = foundIndex - 1
}
2021-04-12 17:59:09 +00:00
// the number of queried messages cannot exceed the MaxPageSize and the total remaining messages i.e., msgList.len-foundIndex
retrievedPageSize = minOf ( int ( pageSize ) , MaxPageSize , remainingMessages )
2021-03-18 16:40:47 +00:00
s = foundIndex + 1 // non inclusive
e = foundIndex + retrievedPageSize
newCursor = msgList [ e ] . index // the new cursor points to the end of the page
2021-04-22 00:09:37 +00:00
case pb . PagingInfo_BACKWARD : // backward pagination
2021-03-18 16:40:47 +00:00
remainingMessages := foundIndex
if initQuery {
2021-04-12 17:59:09 +00:00
remainingMessages = remainingMessages + 1
2021-03-18 16:40:47 +00:00
foundIndex = foundIndex + 1
}
2021-04-12 17:59:09 +00:00
// the number of queried messages cannot exceed the MaxPageSize and the total remaining messages i.e., foundIndex-0
retrievedPageSize = minOf ( int ( pageSize ) , MaxPageSize , remainingMessages )
2021-03-18 16:40:47 +00:00
s = foundIndex - retrievedPageSize
e = foundIndex - 1
2021-10-09 18:18:53 +00:00
newCursor = msgList [ s ] . index // the new cursor points to the beginning of the page
2021-03-18 16:40:47 +00:00
}
// retrieve the messages
for i := s ; i <= e ; i ++ {
resMessages = append ( resMessages , msgList [ i ] )
}
2021-04-22 00:09:37 +00:00
resPagingInfo = & pb . PagingInfo { PageSize : uint64 ( retrievedPageSize ) , Cursor : newCursor , Direction : pinfo . Direction }
2021-03-18 16:40:47 +00:00
return
}
2021-04-22 00:09:37 +00:00
func paginateWithoutIndex ( list [ ] IndexedWakuMessage , pinfo * pb . PagingInfo ) ( resMessages [ ] * pb . WakuMessage , resPinfo * pb . PagingInfo ) {
2021-03-18 16:40:47 +00:00
// takes list, and performs 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
indexedData , updatedPagingInfo := paginateWithIndex ( list , pinfo )
for _ , indexedMsg := range indexedData {
resMessages = append ( resMessages , indexedMsg . msg )
}
resPinfo = updatedPagingInfo
return
}
2021-11-05 20:09:48 +00:00
func ( store * WakuStore ) FindMessages ( query * pb . HistoryQuery ) * pb . HistoryResponse {
2021-04-22 00:09:37 +00:00
result := new ( pb . HistoryResponse )
2021-03-18 16:40:47 +00:00
// data holds IndexedWakuMessage whose topics match the query
var data [ ] IndexedWakuMessage
2021-11-06 22:46:58 +00:00
for indexedMsg := range store . messageQueue . Messages ( ) {
2021-04-15 02:17:53 +00:00
// temporal filtering
// check whether the history query contains a time filter
if query . StartTime != 0 && query . EndTime != 0 {
if indexedMsg . msg . Timestamp < query . StartTime || indexedMsg . msg . Timestamp > query . EndTime {
continue
}
}
2021-04-28 15:11:32 +00:00
// filter based on content filters
// an empty list of contentFilters means no content filter is requested
if len ( query . ContentFilters ) != 0 {
match := false
for _ , cf := range query . ContentFilters {
if cf . ContentTopic == indexedMsg . msg . ContentTopic {
match = true
break
}
}
if ! match {
continue
}
}
// filter based on pubsub topic
// an empty pubsub topic means no pubsub topic filter is requested
if query . PubsubTopic != "" {
if indexedMsg . pubsubTopic != query . PubsubTopic {
continue
2021-04-20 21:46:35 +00:00
}
2021-03-18 16:40:47 +00:00
}
2021-04-20 21:46:35 +00:00
2021-04-28 15:11:32 +00:00
// Some criteria matched
data = append ( data , indexedMsg )
2021-03-18 16:40:47 +00:00
}
result . Messages , result . PagingInfo = paginateWithoutIndex ( data , query . PagingInfo )
return result
}
type MessageProvider interface {
2021-10-25 19:41:08 +00:00
GetAll ( ) ( [ ] persistence . StoredMessage , error )
2021-04-28 15:11:32 +00:00
Put ( cursor * pb . Index , pubsubTopic string , message * pb . WakuMessage ) error
2021-04-12 17:59:09 +00:00
Stop ( )
2021-03-18 16:40:47 +00:00
}
2021-10-31 19:00:38 +00:00
type Query struct {
Topic string
ContentTopics [ ] string
2022-02-23 15:01:53 +00:00
StartTime int64
EndTime int64
2021-10-31 19:00:38 +00:00
}
2021-11-05 20:09:48 +00:00
// Result represents a valid response from a store node
2021-10-31 19:00:38 +00:00
type Result struct {
Messages [ ] * pb . WakuMessage
query * pb . HistoryQuery
cursor * pb . Index
peerId peer . ID
}
func ( r * Result ) Cursor ( ) * pb . Index {
return r . cursor
}
func ( r * Result ) PeerID ( ) peer . ID {
return r . peerId
}
func ( r * Result ) Query ( ) * pb . HistoryQuery {
return r . query
}
2021-03-18 16:40:47 +00:00
type IndexedWakuMessage struct {
2021-04-28 15:11:32 +00:00
msg * pb . WakuMessage
index * pb . Index
pubsubTopic string
2021-03-18 16:40:47 +00:00
}
type WakuStore struct {
2021-11-05 14:27:30 +00:00
ctx context . Context
MsgC chan * protocol . Envelope
2021-11-23 15:03:12 +00:00
wg * sync . WaitGroup
2021-07-11 18:11:38 +00:00
2022-01-18 18:17:06 +00:00
log * zap . SugaredLogger
2021-11-01 12:38:03 +00:00
started bool
2021-11-06 20:57:19 +00:00
messageQueue * MessageQueue
msgProvider MessageProvider
h host . Host
2021-12-06 10:49:13 +00:00
swap * swap . WakuSwap
2021-03-18 16:40:47 +00:00
}
2021-11-05 20:09:48 +00:00
// NewWakuStore creates a WakuStore using an specific MessageProvider for storing the messages
2022-01-18 18:17:06 +00:00
func NewWakuStore ( host host . Host , swap * swap . WakuSwap , p MessageProvider , maxNumberOfMessages int , maxRetentionDuration time . Duration , log * zap . SugaredLogger ) * WakuStore {
2021-03-18 16:40:47 +00:00
wakuStore := new ( WakuStore )
wakuStore . msgProvider = p
2021-11-09 16:18:57 +00:00
wakuStore . h = host
2021-12-06 10:49:13 +00:00
wakuStore . swap = swap
2021-11-23 15:03:12 +00:00
wakuStore . wg = & sync . WaitGroup { }
2022-01-18 18:17:06 +00:00
wakuStore . log = log . Named ( "store" )
2021-11-06 20:57:19 +00:00
wakuStore . messageQueue = NewMessageQueue ( maxNumberOfMessages , maxRetentionDuration )
2021-03-18 23:21:45 +00:00
return wakuStore
}
2021-03-18 16:40:47 +00:00
2021-11-05 20:09:48 +00:00
// SetMessageProvider allows switching the message provider used with a WakuStore
func ( store * WakuStore ) SetMessageProvider ( p MessageProvider ) {
2021-04-18 23:41:42 +00:00
store . msgProvider = p
}
2021-11-05 20:09:48 +00:00
// Start initializes the WakuStore by enabling the protocol and fetching records from a message provider
2021-11-09 16:18:57 +00:00
func ( store * WakuStore ) Start ( ctx context . Context ) {
2021-11-01 12:38:03 +00:00
if store . started {
2021-04-07 21:16:29 +00:00
return
}
2021-11-01 12:38:03 +00:00
store . started = true
store . ctx = ctx
2021-11-04 16:19:59 +00:00
store . MsgC = make ( chan * protocol . Envelope , 1024 )
2021-11-01 12:38:03 +00:00
2022-02-23 15:06:47 +00:00
store . h . SetStreamHandlerMatch ( StoreID_v20beta4 , protocol . PrefixTextMatch ( string ( StoreID_v20beta4 ) ) , store . onRequest )
2021-03-18 16:40:47 +00:00
2021-11-23 15:03:12 +00:00
store . wg . Add ( 1 )
2021-06-28 13:20:23 +00:00
go store . storeIncomingMessages ( ctx )
2021-04-19 00:03:16 +00:00
if store . msgProvider == nil {
2022-01-18 18:17:06 +00:00
store . log . Info ( "Store protocol started (no message provider)" )
2021-04-19 00:03:16 +00:00
return
}
2021-10-25 19:41:08 +00:00
store . fetchDBRecords ( ctx )
2022-01-18 18:17:06 +00:00
store . log . Info ( "Store protocol started" )
2021-10-25 19:41:08 +00:00
}
func ( store * WakuStore ) fetchDBRecords ( ctx context . Context ) {
2021-11-01 12:38:03 +00:00
if store . msgProvider == nil {
return
}
storedMessages , err := ( store . msgProvider ) . GetAll ( )
2021-03-22 16:45:13 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "could not load DBProvider messages" , err )
2021-10-30 23:19:03 +00:00
metrics . RecordStoreError ( ctx , "store_load_failure" )
2021-03-22 16:45:13 +00:00
return
}
2021-07-11 18:11:38 +00:00
for _ , storedMessage := range storedMessages {
idx := & pb . Index {
Digest : storedMessage . ID ,
2022-02-23 15:01:53 +00:00
ReceiverTime : storedMessage . ReceiverTime ,
2021-03-22 16:45:13 +00:00
}
2021-07-11 18:11:38 +00:00
2022-01-18 18:17:06 +00:00
_ = store . addToMessageQueue ( storedMessage . PubsubTopic , idx , storedMessage . Message )
2021-06-28 13:20:23 +00:00
2021-11-06 20:57:19 +00:00
metrics . RecordMessage ( ctx , "stored" , store . messageQueue . Length ( ) )
2021-03-22 16:45:13 +00:00
}
2021-03-18 16:40:47 +00:00
}
2022-01-18 18:17:06 +00:00
func ( store * WakuStore ) addToMessageQueue ( pubsubTopic string , idx * pb . Index , msg * pb . WakuMessage ) error {
return store . messageQueue . Push ( IndexedWakuMessage { msg : msg , index : idx , pubsubTopic : pubsubTopic } )
2021-07-11 18:11:38 +00:00
}
2022-02-23 15:01:53 +00:00
func ( store * WakuStore ) storeMessage ( env * protocol . Envelope ) error {
2021-11-05 20:09:48 +00:00
index , err := computeIndex ( env )
2021-06-10 13:00:06 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "could not calculate message index" , err )
2022-02-23 15:01:53 +00:00
return err
2021-06-10 13:00:06 +00:00
}
2021-03-18 16:40:47 +00:00
2022-01-18 18:17:06 +00:00
err = store . addToMessageQueue ( env . PubsubTopic ( ) , index , env . Message ( ) )
if err == ErrDuplicatedMessage {
2022-02-23 15:01:53 +00:00
return err
2022-01-18 18:17:06 +00:00
}
2021-03-18 16:40:47 +00:00
2021-06-10 13:00:06 +00:00
if store . msgProvider == nil {
2021-11-06 20:57:19 +00:00
metrics . RecordMessage ( store . ctx , "stored" , store . messageQueue . Length ( ) )
2022-02-23 15:01:53 +00:00
return err
2021-06-10 13:00:06 +00:00
}
2021-03-18 16:40:47 +00:00
2021-11-05 14:27:30 +00:00
// TODO: Move this to a separate go routine if DB writes becomes a bottleneck
2021-11-05 20:09:48 +00:00
err = store . msgProvider . Put ( index , env . PubsubTopic ( ) , env . Message ( ) ) // Should the index be stored?
2021-06-10 13:00:06 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "could not store message" , err )
2021-10-30 23:19:03 +00:00
metrics . RecordStoreError ( store . ctx , "store_failure" )
2022-02-23 15:01:53 +00:00
return err
2021-06-10 13:00:06 +00:00
}
2021-06-28 13:20:23 +00:00
2021-11-06 20:57:19 +00:00
metrics . RecordMessage ( store . ctx , "stored" , store . messageQueue . Length ( ) )
2022-02-23 15:01:53 +00:00
return nil
2021-06-10 13:00:06 +00:00
}
2021-06-28 13:20:23 +00:00
func ( store * WakuStore ) storeIncomingMessages ( ctx context . Context ) {
2021-11-23 15:03:12 +00:00
defer store . wg . Done ( )
2021-06-10 13:00:06 +00:00
for envelope := range store . MsgC {
2022-02-23 15:01:53 +00:00
_ = store . storeMessage ( envelope )
2021-03-18 16:40:47 +00:00
}
}
func ( store * WakuStore ) onRequest ( s network . Stream ) {
defer s . Close ( )
2021-04-22 00:09:37 +00:00
historyRPCRequest := & pb . HistoryRPC { }
2021-03-18 23:21:45 +00:00
2021-04-07 21:16:29 +00:00
writer := protoio . NewDelimitedWriter ( s )
2021-11-25 14:18:33 +00:00
reader := protoio . NewDelimitedReader ( s , math . MaxInt32 )
2021-03-18 23:21:45 +00:00
2021-04-07 21:16:29 +00:00
err := reader . ReadMsg ( historyRPCRequest )
2021-03-18 16:40:47 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "error reading request" , err )
2021-10-30 23:19:03 +00:00
metrics . RecordStoreError ( store . ctx , "decodeRPCFailure" )
2021-03-18 16:40:47 +00:00
return
}
2022-01-18 18:17:06 +00:00
store . log . Info ( fmt . Sprintf ( "%s: Received query from %s" , s . Conn ( ) . LocalPeer ( ) , s . Conn ( ) . RemotePeer ( ) ) )
2021-03-18 16:40:47 +00:00
2021-04-22 00:09:37 +00:00
historyResponseRPC := & pb . HistoryRPC { }
2021-03-18 16:40:47 +00:00
historyResponseRPC . RequestId = historyRPCRequest . RequestId
historyResponseRPC . Response = store . FindMessages ( historyRPCRequest . Query )
2021-04-07 21:16:29 +00:00
err = writer . WriteMsg ( historyResponseRPC )
2021-03-18 16:40:47 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "error writing response" , err )
2021-07-29 12:40:54 +00:00
_ = s . Reset ( )
2021-03-18 16:40:47 +00:00
} else {
2022-01-18 18:17:06 +00:00
store . log . Info ( fmt . Sprintf ( "%s: Response sent to %s" , s . Conn ( ) . LocalPeer ( ) . String ( ) , s . Conn ( ) . RemotePeer ( ) . String ( ) ) )
2021-03-18 16:40:47 +00:00
}
}
2021-11-05 20:09:48 +00:00
func computeIndex ( env * protocol . Envelope ) ( * pb . Index , error ) {
2021-04-22 00:09:37 +00:00
return & pb . Index {
2021-11-05 20:09:48 +00:00
Digest : env . Hash ( ) ,
2021-10-12 13:12:54 +00:00
ReceiverTime : utils . GetUnixEpoch ( ) ,
2021-11-05 20:09:48 +00:00
SenderTime : env . Message ( ) . Timestamp ,
2022-03-02 16:02:13 +00:00
PubsubTopic : env . PubsubTopic ( ) ,
2021-03-18 16:40:47 +00:00
} , nil
}
2021-04-22 00:09:37 +00:00
func indexComparison ( x , y * pb . Index ) int {
2021-03-18 16:40:47 +00:00
// compares x and y
// returns 0 if they are equal
// returns -1 if x < y
// returns 1 if x > y
2021-07-11 18:11:38 +00:00
var timecmp int = 0
2021-03-18 16:40:47 +00:00
2022-03-02 16:02:13 +00:00
if x . SenderTime != 0 && y . SenderTime != 0 {
if x . SenderTime > y . SenderTime {
timecmp = 1
} else if x . SenderTime < y . SenderTime {
timecmp = - 1
}
}
2021-03-18 16:40:47 +00:00
if timecmp != 0 {
return timecmp // timestamp has a higher priority for comparison
}
2022-03-02 16:02:13 +00:00
digestcm := bytes . Compare ( x . Digest , y . Digest )
if digestcm != 0 {
return digestcm
}
pubsubTopicCmp := strings . Compare ( x . PubsubTopic , y . PubsubTopic )
if pubsubTopicCmp != 0 {
return pubsubTopicCmp
}
// receiverTimestamp (a fallback only if senderTimestamp unset on either side, and all other fields unequal)
if x . ReceiverTime > y . ReceiverTime {
timecmp = 1
} else if x . ReceiverTime < y . ReceiverTime {
timecmp = - 1
}
return timecmp
2021-03-18 16:40:47 +00:00
}
func 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
return indexComparison ( x . index , y . index )
}
2021-04-22 00:09:37 +00:00
func findIndex ( msgList [ ] IndexedWakuMessage , index * pb . Index ) int {
2021-03-18 16:40:47 +00:00
// returns the position of an IndexedWakuMessage in msgList whose index value matches the given index
// returns -1 if no match is found
2021-04-04 18:42:08 +00:00
for i , indexedWakuMessage := range msgList {
2022-03-02 16:02:13 +00:00
if bytes . Equal ( indexedWakuMessage . index . Digest , index . Digest ) && indexedWakuMessage . index . SenderTime == index . SenderTime && indexedWakuMessage . index . PubsubTopic == index . PubsubTopic {
2021-03-18 16:40:47 +00:00
return i
}
}
return - 1
}
2021-04-15 02:17:53 +00:00
type HistoryRequestParameters struct {
2021-04-15 17:55:40 +00:00
selectedPeer peer . ID
2021-04-15 02:17:53 +00:00
requestId [ ] byte
2021-04-22 00:09:37 +00:00
cursor * pb . Index
pageSize uint64
asc bool
2021-04-15 02:17:53 +00:00
s * WakuStore
}
type HistoryRequestOption func ( * HistoryRequestParameters )
2021-11-05 20:09:48 +00:00
// WithPeer is an option used to specify the peerID to request the message history
2021-04-15 17:55:40 +00:00
func WithPeer ( p peer . ID ) HistoryRequestOption {
2021-04-15 02:17:53 +00:00
return func ( params * HistoryRequestParameters ) {
2021-04-15 17:55:40 +00:00
params . selectedPeer = p
2021-04-15 02:17:53 +00:00
}
}
2021-11-05 20:09:48 +00:00
// WithAutomaticPeerSelection is an option used to randomly select a peer from the store
// to request the message history
2021-04-15 02:17:53 +00:00
func WithAutomaticPeerSelection ( ) HistoryRequestOption {
return func ( params * HistoryRequestParameters ) {
2022-02-23 15:06:47 +00:00
p , err := utils . SelectPeer ( params . s . h , string ( StoreID_v20beta4 ) , params . s . log )
2021-06-16 10:14:22 +00:00
if err == nil {
params . selectedPeer = * p
} else {
2022-01-18 18:17:06 +00:00
params . s . log . Info ( "Error selecting peer: " , err )
2021-06-16 10:14:22 +00:00
}
2021-04-15 02:17:53 +00:00
}
}
2021-11-09 23:34:04 +00:00
func WithFastestPeerSelection ( ctx context . Context ) HistoryRequestOption {
return func ( params * HistoryRequestParameters ) {
2022-02-23 15:06:47 +00:00
p , err := utils . SelectPeerWithLowestRTT ( ctx , params . s . h , string ( StoreID_v20beta4 ) , params . s . log )
2021-11-09 23:34:04 +00:00
if err == nil {
params . selectedPeer = * p
} else {
2022-01-18 18:17:06 +00:00
params . s . log . Info ( "Error selecting peer: " , err )
2021-11-09 23:34:04 +00:00
}
}
}
2021-04-15 02:17:53 +00:00
func WithRequestId ( requestId [ ] byte ) HistoryRequestOption {
return func ( params * HistoryRequestParameters ) {
params . requestId = requestId
}
}
func WithAutomaticRequestId ( ) HistoryRequestOption {
return func ( params * HistoryRequestParameters ) {
2021-04-28 20:10:44 +00:00
params . requestId = protocol . GenerateRequestId ( )
2021-04-15 02:17:53 +00:00
}
}
2021-04-22 00:09:37 +00:00
func WithCursor ( c * pb . Index ) HistoryRequestOption {
2021-04-15 02:17:53 +00:00
return func ( params * HistoryRequestParameters ) {
params . cursor = c
}
}
2021-11-05 20:09:48 +00:00
// WithPaging is an option used to specify the order and maximum number of records to return
2021-04-15 02:17:53 +00:00
func WithPaging ( asc bool , pageSize uint64 ) HistoryRequestOption {
return func ( params * HistoryRequestParameters ) {
params . asc = asc
params . pageSize = pageSize
}
}
2021-11-05 20:09:48 +00:00
// Default options to be used when querying a store node for results
2021-04-15 02:17:53 +00:00
func DefaultOptions ( ) [ ] HistoryRequestOption {
return [ ] HistoryRequestOption {
WithAutomaticRequestId ( ) ,
WithAutomaticPeerSelection ( ) ,
2021-11-05 20:09:48 +00:00
WithPaging ( true , MaxPageSize ) ,
2021-04-15 02:17:53 +00:00
}
}
2021-06-10 13:00:06 +00:00
func ( store * WakuStore ) queryFrom ( ctx context . Context , q * pb . HistoryQuery , selectedPeer peer . ID , requestId [ ] byte ) ( * pb . HistoryResponse , error ) {
2022-01-18 18:17:06 +00:00
store . log . Info ( fmt . Sprintf ( "Querying message history with peer %s" , selectedPeer ) )
2021-10-10 15:46:00 +00:00
2022-02-23 15:06:47 +00:00
connOpt , err := store . h . NewStream ( ctx , selectedPeer , StoreID_v20beta4 )
2021-06-10 13:00:06 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "Failed to connect to remote peer" , err )
2021-06-10 13:00:06 +00:00
return nil , err
}
defer connOpt . Close ( )
2021-07-29 12:40:54 +00:00
defer func ( ) {
_ = connOpt . Reset ( )
} ( )
2021-06-10 13:00:06 +00:00
historyRequest := & pb . HistoryRPC { Query : q , RequestId : hex . EncodeToString ( requestId ) }
writer := protoio . NewDelimitedWriter ( connOpt )
2021-11-25 14:18:33 +00:00
reader := protoio . NewDelimitedReader ( connOpt , math . MaxInt32 )
2021-06-10 13:00:06 +00:00
err = writer . WriteMsg ( historyRequest )
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "could not write request" , err )
2021-06-10 13:00:06 +00:00
return nil , err
}
historyResponseRPC := & pb . HistoryRPC { }
err = reader . ReadMsg ( historyResponseRPC )
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "could not read response" , err )
2021-10-30 23:19:03 +00:00
metrics . RecordStoreError ( store . ctx , "decodeRPCFailure" )
2021-06-10 13:00:06 +00:00
return nil , err
}
2021-11-06 20:57:19 +00:00
metrics . RecordMessage ( ctx , "retrieved" , store . messageQueue . Length ( ) )
2021-06-28 13:20:23 +00:00
2021-06-10 13:00:06 +00:00
return historyResponseRPC . Response , nil
}
2021-10-31 19:00:38 +00:00
func ( store * WakuStore ) Query ( ctx context . Context , query Query , opts ... HistoryRequestOption ) ( * Result , error ) {
q := & pb . HistoryQuery {
PubsubTopic : query . Topic ,
ContentFilters : [ ] * pb . ContentFilter { } ,
StartTime : query . StartTime ,
EndTime : query . EndTime ,
PagingInfo : & pb . PagingInfo { } ,
}
for _ , cf := range query . ContentTopics {
q . ContentFilters = append ( q . ContentFilters , & pb . ContentFilter { ContentTopic : cf } )
}
2021-04-15 02:17:53 +00:00
params := new ( HistoryRequestParameters )
params . s = store
2021-04-28 20:10:44 +00:00
optList := DefaultOptions ( )
optList = append ( optList , opts ... )
for _ , opt := range optList {
2021-04-15 02:17:53 +00:00
opt ( params )
}
2021-04-15 17:55:40 +00:00
if params . selectedPeer == "" {
2021-04-15 02:17:53 +00:00
return nil , ErrNoPeersAvailable
}
if len ( params . requestId ) == 0 {
return nil , ErrInvalidId
}
if params . cursor != nil {
q . PagingInfo . Cursor = params . cursor
}
if params . asc {
2021-04-22 00:09:37 +00:00
q . PagingInfo . Direction = pb . PagingInfo_FORWARD
2021-04-15 02:17:53 +00:00
} else {
2021-04-22 00:09:37 +00:00
q . PagingInfo . Direction = pb . PagingInfo_BACKWARD
2021-03-18 16:40:47 +00:00
}
2021-04-15 02:17:53 +00:00
q . PagingInfo . PageSize = params . pageSize
2021-03-18 16:40:47 +00:00
2021-10-31 19:00:38 +00:00
response , err := store . queryFrom ( ctx , q , params . selectedPeer , params . requestId )
if err != nil {
return nil , err
}
if response . Error == pb . HistoryResponse_INVALID_CURSOR {
return nil , errors . New ( "invalid cursor" )
}
return & Result {
Messages : response . Messages ,
cursor : response . PagingInfo . Cursor ,
query : q ,
peerId : params . selectedPeer ,
} , nil
}
2021-11-05 20:09:48 +00:00
// Next is used with to retrieve the next page of rows from a query response.
// If no more records are found, the result will not contain any messages.
// This function is useful for iterating over results without having to manually
// specify the cursor and pagination order and max number of results
2021-10-31 19:00:38 +00:00
func ( store * WakuStore ) Next ( ctx context . Context , r * Result ) ( * Result , error ) {
q := & pb . HistoryQuery {
PubsubTopic : r . query . PubsubTopic ,
ContentFilters : r . query . ContentFilters ,
StartTime : r . query . StartTime ,
EndTime : r . query . EndTime ,
PagingInfo : & pb . PagingInfo {
PageSize : r . query . PagingInfo . PageSize ,
Direction : r . query . PagingInfo . Direction ,
Cursor : & pb . Index {
Digest : r . cursor . Digest ,
ReceiverTime : r . cursor . ReceiverTime ,
SenderTime : r . cursor . SenderTime ,
2022-03-02 16:02:13 +00:00
PubsubTopic : r . cursor . PubsubTopic ,
2021-10-31 19:00:38 +00:00
} ,
} ,
}
response , err := store . queryFrom ( ctx , q , r . peerId , protocol . GenerateRequestId ( ) )
if err != nil {
return nil , err
}
if response . Error == pb . HistoryResponse_INVALID_CURSOR {
return nil , errors . New ( "invalid cursor" )
}
return & Result {
Messages : response . Messages ,
cursor : response . PagingInfo . Cursor ,
query : q ,
peerId : r . peerId ,
} , nil
2021-06-10 13:00:06 +00:00
}
2022-01-18 18:17:06 +00:00
func ( store * WakuStore ) queryLoop ( ctx context . Context , query * pb . HistoryQuery , candidateList [ ] peer . ID ) ( [ ] * pb . WakuMessage , error ) {
2021-06-10 13:00:06 +00:00
// loops through the candidateList in order and sends the query to each until one of the query gets resolved successfully
// returns the number of retrieved messages, or error if all the requests fail
2022-01-18 18:17:06 +00:00
queryWg := sync . WaitGroup { }
queryWg . Add ( len ( candidateList ) )
resultChan := make ( chan * pb . HistoryResponse , len ( candidateList ) )
2021-06-10 13:00:06 +00:00
for _ , peer := range candidateList {
2022-01-18 18:17:06 +00:00
func ( ) {
defer queryWg . Done ( )
result , err := store . queryFrom ( ctx , query , peer , protocol . GenerateRequestId ( ) )
if err == nil {
resultChan <- result
2022-02-23 15:01:53 +00:00
return
2022-01-18 18:17:06 +00:00
}
store . log . Error ( fmt . Errorf ( "resume history with peer %s failed: %w" , peer , err ) )
} ( )
}
queryWg . Wait ( )
close ( resultChan )
var messages [ ] * pb . WakuMessage
hasResults := false
for result := range resultChan {
hasResults = true
messages = append ( messages , result . Messages ... )
}
if hasResults {
return messages , nil
2021-03-18 16:40:47 +00:00
}
2021-06-10 13:00:06 +00:00
return nil , ErrFailedQuery
}
2022-02-23 15:01:53 +00:00
func ( store * WakuStore ) findLastSeen ( ) int64 {
var lastSeenTime int64 = 0
2021-11-06 22:46:58 +00:00
for imsg := range store . messageQueue . Messages ( ) {
2021-06-10 13:00:06 +00:00
if imsg . msg . Timestamp > lastSeenTime {
lastSeenTime = imsg . msg . Timestamp
}
}
return lastSeenTime
}
2021-03-18 16:40:47 +00:00
2022-02-23 15:01:53 +00:00
func max ( x , y int64 ) int64 {
if x > y {
return x
}
return y
}
2021-11-05 20:09:48 +00:00
// Resume retrieves the history of waku messages published on the default waku pubsub topic since the last time the waku store node has been online
2021-06-10 13:00:06 +00:00
// messages are stored in the store node's messages field and in the message db
// the offline time window is measured as the difference between the current time and the timestamp of the most recent persisted waku message
// an offset of 20 second is added to the time window to count for nodes asynchrony
// the history is fetched from one of the peers persisted in the waku store node's peer manager unit
// peerList indicates the list of peers to query from. The history is fetched from the first available peer in this list. Such candidates should be found through a discovery method (to be developed).
// if no peerList is passed, one of the peers in the underlying peer manager unit of the store protocol is picked randomly to fetch the history from. The history gets fetched successfully if the dialed peer has been online during the queried time window.
// the resume proc returns the number of retrieved messages if no error occurs, otherwise returns the error string
2021-10-09 18:18:53 +00:00
func ( store * WakuStore ) Resume ( ctx context . Context , pubsubTopic string , peerList [ ] peer . ID ) ( int , error ) {
2021-11-01 12:38:03 +00:00
if ! store . started {
return 0 , errors . New ( "can't resume: store has not started" )
}
2021-10-12 13:12:54 +00:00
currentTime := utils . GetUnixEpoch ( )
2021-06-10 13:00:06 +00:00
lastSeenTime := store . findLastSeen ( )
2022-02-23 15:01:53 +00:00
var offset int64 = int64 ( 20 * time . Nanosecond )
2021-06-10 13:00:06 +00:00
currentTime = currentTime + offset
2022-02-23 15:01:53 +00:00
lastSeenTime = max ( lastSeenTime - offset , 0 )
2021-06-10 13:00:06 +00:00
rpc := & pb . HistoryQuery {
PubsubTopic : pubsubTopic ,
StartTime : lastSeenTime ,
EndTime : currentTime ,
PagingInfo : & pb . PagingInfo {
PageSize : 0 ,
Direction : pb . PagingInfo_BACKWARD ,
} ,
}
2021-04-07 21:16:29 +00:00
2021-12-08 14:21:30 +00:00
if len ( peerList ) == 0 {
2022-02-23 15:06:47 +00:00
p , err := utils . SelectPeer ( store . h , string ( StoreID_v20beta4 ) , store . log )
2021-06-16 10:14:22 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Info ( "Error selecting peer: " , err )
2021-06-10 13:00:06 +00:00
return - 1 , ErrNoPeersAvailable
}
2021-03-18 16:40:47 +00:00
2021-12-08 14:21:30 +00:00
peerList = append ( peerList , * p )
}
2022-01-18 18:17:06 +00:00
messages , err := store . queryLoop ( ctx , rpc , peerList )
2021-12-08 14:21:30 +00:00
if err != nil {
2022-01-18 18:17:06 +00:00
store . log . Error ( "failed to resume history" , err )
2021-12-08 14:21:30 +00:00
return - 1 , ErrFailedToResumeHistory
2021-03-18 16:40:47 +00:00
}
2022-02-23 15:01:53 +00:00
msgCount := 0
2022-01-18 18:17:06 +00:00
for _ , msg := range messages {
2022-02-23 15:01:53 +00:00
if err = store . storeMessage ( protocol . NewEnvelope ( msg , pubsubTopic ) ) ; err == nil {
msgCount ++
}
2021-03-18 16:40:47 +00:00
}
2022-01-18 18:17:06 +00:00
store . log . Info ( "Retrieved messages since the last online time: " , len ( messages ) )
2021-11-01 12:38:03 +00:00
2022-02-23 15:01:53 +00:00
return msgCount , nil
2021-03-18 16:40:47 +00:00
}
// TODO: queryWithAccounting
2021-10-11 22:45:54 +00:00
2021-11-05 20:09:48 +00:00
// Stop closes the store message channel and removes the protocol stream handler
func ( store * WakuStore ) Stop ( ) {
store . started = false
2021-11-01 14:42:55 +00:00
2021-11-05 20:09:48 +00:00
if store . MsgC != nil {
close ( store . MsgC )
2021-11-01 14:42:55 +00:00
}
2021-11-05 20:09:48 +00:00
if store . h != nil {
2022-02-23 15:06:47 +00:00
store . h . RemoveStreamHandler ( StoreID_v20beta4 )
2021-11-01 14:42:55 +00:00
}
2021-11-23 15:03:12 +00:00
store . wg . Wait ( )
2021-10-11 22:45:54 +00:00
}