2020-11-24 04:34:32 +00:00
## Waku Store protocol for historical messaging support.
## See spec for more details:
## https://github.com/vacp2p/specs/blob/master/specs/waku/v2/waku-store.md
2021-06-09 14:37:08 +00:00
{. push raises : [ Defect ] . }
2020-08-31 03:32:41 +00:00
import
2022-06-28 23:59:38 +00:00
std / [ tables , times , sequtils , options , math ] ,
2022-08-01 16:21:11 +00:00
stew / results ,
2021-07-16 22:28:35 +00:00
chronicles ,
chronos ,
2022-09-07 15:31:27 +00:00
bearssl / rand ,
2020-09-28 21:44:14 +00:00
libp2p / crypto / crypto ,
2020-08-31 03:32:41 +00:00
libp2p / protocols / protocol ,
libp2p / protobuf / minprotobuf ,
libp2p / stream / connection ,
2022-07-25 11:01:37 +00:00
metrics
import
2021-03-25 08:37:11 +00:00
.. / .. / node / storage / message / message_store ,
2022-09-16 10:55:22 +00:00
.. / .. / node / storage / message / message_retention_policy ,
2022-07-25 11:01:37 +00:00
.. / .. / node / storage / message / waku_store_queue ,
2021-07-16 22:28:35 +00:00
.. / .. / node / peer_manager / peer_manager ,
2022-02-17 15:00:15 +00:00
.. / .. / utils / time ,
2022-07-25 11:01:37 +00:00
.. / .. / utils / pagination ,
.. / .. / utils / requests ,
.. / waku_message ,
2021-07-16 22:28:35 +00:00
.. / waku_swap / waku_swap ,
2022-07-25 11:01:37 +00:00
. / rpc ,
. / rpc_codec
2020-11-24 04:34:32 +00:00
2020-08-27 02:44:09 +00:00
2021-03-04 07:19:21 +00:00
declarePublicGauge waku_store_messages , " number of historical messages " , [ " type " ]
2021-01-29 08:42:41 +00:00
declarePublicGauge waku_store_peers , " number of store peers "
declarePublicGauge waku_store_errors , " number of store protocol errors " , [ " type " ]
2022-01-06 11:23:25 +00:00
declarePublicGauge waku_store_queries , " number of store queries received "
2022-09-14 12:40:11 +00:00
declarePublicHistogram waku_store_insert_duration_seconds , " message insertion duration "
declarePublicHistogram waku_store_query_duration_seconds , " history query duration "
2021-01-29 08:42:41 +00:00
2020-09-16 04:23:10 +00:00
logScope :
topics = " wakustore "
2022-08-01 16:21:11 +00:00
const
WakuStoreCodec * = " /vac/waku/store/2.0.0-beta4 "
DefaultTopic * = " /waku/2/default-waku/proto "
2022-07-25 11:01:37 +00:00
# Constants required for pagination -------------------------------------------
2022-08-01 12:09:41 +00:00
MaxPageSize * = StoreMaxPageSize
2022-07-25 11:01:37 +00:00
# TODO the DefaultPageSize can be changed, it's current value is random
DefaultPageSize * = uint64 ( 20 ) # A recommended default number of waku messages per page
2022-08-01 12:09:41 +00:00
MaxTimeVariance * = StoreMaxTimeVariance
2022-07-25 11:01:37 +00:00
2022-08-01 16:21:11 +00:00
const MaxRpcSize = StoreMaxPageSize * MaxWakuMessageSize + 64 * 1024 # We add a 64kB safety buffer for protocol overhead
2022-07-25 11:01:37 +00:00
2020-08-27 02:44:09 +00:00
2021-02-09 08:31:38 +00:00
# Error types (metric label values)
const
2022-09-13 16:06:23 +00:00
insertFailure = " insert_failure "
2021-02-09 08:31:38 +00:00
dialFailure = " dial_failure "
decodeRpcFailure = " decode_rpc_failure "
2022-08-01 16:21:11 +00:00
peerNotFoundFailure = " peer_not_found_failure "
2021-02-09 08:31:38 +00:00
2022-06-13 17:59:53 +00:00
type
2022-08-01 12:09:41 +00:00
WakuStoreResult * [ T ] = Result [ T , string ]
2022-06-13 17:59:53 +00:00
WakuStore * = ref object of LPProtocol
peerManager * : PeerManager
2022-09-07 15:31:27 +00:00
rng * : ref rand . HmacDrbgContext
2022-06-13 17:59:53 +00:00
messages * : StoreQueueRef # in-memory message store
store * : MessageStore # sqlite DB handle
wakuSwap * : WakuSwap
persistMessages * : bool
2022-09-13 11:36:04 +00:00
#TODO: SqliteStore currenly also holds isSqliteOnly; put it in single place.
2022-06-13 17:59:53 +00:00
isSqliteOnly : bool # if true, don't use in memory-store and answer history queries from the sqlite DB
2022-09-16 10:55:22 +00:00
retentionPolicy : Option [ MessageRetentionPolicy ]
2022-06-13 17:59:53 +00:00
2022-09-15 16:13:30 +00:00
proc reportMessagesCountMetric ( store : MessageStore ) =
let resCount = store . getMessagesCount ( )
if resCount . isErr ( ) :
return
waku_store_messages . set ( resCount . value , labelValues = [ " stored " ] )
2022-02-17 10:00:45 +00:00
proc findMessages ( w : WakuStore , query : HistoryQuery ) : HistoryResponse {. gcsafe . } =
## Query history to return a single page of messages matching the query
2022-03-11 05:26:15 +00:00
2022-09-02 08:14:58 +00:00
# Extract query criteria. All query criteria are optional
2022-01-11 13:32:09 +00:00
let
qContentTopics = if ( query . contentFilters . len ! = 0 ) : some ( query . contentFilters . mapIt ( it . contentTopic ) )
else : none ( seq [ ContentTopic ] )
qPubSubTopic = if ( query . pubsubTopic ! = " " ) : some ( query . pubsubTopic )
else : none ( string )
2022-08-01 16:21:11 +00:00
qCursor = if query . pagingInfo . cursor ! = Index ( ) : some ( query . pagingInfo . cursor )
else : none ( Index )
2022-02-17 15:00:15 +00:00
qStartTime = if query . startTime ! = Timestamp ( 0 ) : some ( query . startTime )
else : none ( Timestamp )
qEndTime = if query . endTime ! = Timestamp ( 0 ) : some ( query . endTime )
else : none ( Timestamp )
2022-08-01 16:21:11 +00:00
qMaxPageSize = query . pagingInfo . pageSize
qAscendingOrder = query . pagingInfo . direction = = PagingDirection . FORWARD
2022-09-13 14:48:33 +00:00
let queryStartTime = getTime ( ) . toUnixFloat ( )
2022-08-01 16:21:11 +00:00
let queryRes = block :
2022-09-13 14:48:33 +00:00
# TODO: Move this logic, together with the insert message logic and load messages on boot
# into a "dual-store" message store implementation.
2022-08-01 16:21:11 +00:00
if w . isSqliteOnly :
w . store . getMessagesByHistoryQuery (
contentTopic = qContentTopics ,
pubsubTopic = qPubSubTopic ,
cursor = qCursor ,
startTime = qStartTime ,
endTime = qEndTime ,
maxPageSize = qMaxPageSize ,
ascendingOrder = qAscendingOrder
)
else :
w . messages . getMessagesByHistoryQuery (
contentTopic = qContentTopics ,
pubsubTopic = qPubSubTopic ,
cursor = qCursor ,
startTime = qStartTime ,
endTime = qEndTime ,
maxPageSize = qMaxPageSize ,
ascendingOrder = qAscendingOrder
)
2022-09-13 14:48:33 +00:00
2022-09-14 12:40:11 +00:00
let queryDuration = getTime ( ) . toUnixFloat ( ) - queryStartTime
waku_store_query_duration_seconds . observe ( queryDuration )
2022-09-13 14:48:33 +00:00
2022-09-02 08:14:58 +00:00
# Build response
# TODO: Improve error reporting
2022-08-01 16:21:11 +00:00
if queryRes . isErr ( ) :
return HistoryResponse ( messages : @ [ ] , pagingInfo : PagingInfo ( ) , error : HistoryResponseError . INVALID_CURSOR )
2022-01-11 13:32:09 +00:00
2022-08-01 16:21:11 +00:00
let ( messages , updatedPagingInfo ) = queryRes . get ( )
2022-01-11 13:32:09 +00:00
2022-08-01 16:21:11 +00:00
HistoryResponse (
messages : messages ,
pagingInfo : updatedPagingInfo . get ( PagingInfo ( ) ) ,
error : HistoryResponseError . NONE
)
2020-11-09 04:48:09 +00:00
2022-08-01 16:21:11 +00:00
proc init * ( ws : WakuStore , capacity = StoreDefaultCapacity ) =
2021-11-03 10:59:51 +00:00
2021-06-09 14:37:08 +00:00
proc handler ( conn : Connection , proto : string ) {. async . } =
2022-09-02 08:14:58 +00:00
let buf = await conn . readLp ( MaxRpcSize . int )
let resReq = HistoryRPC . init ( buf )
if resReq . isErr ( ) :
error " failed to decode rpc " , peerId = conn . peerId
2021-02-09 08:31:38 +00:00
waku_store_errors . inc ( labelValues = [ decodeRpcFailure ] )
2020-08-27 02:44:09 +00:00
return
2022-09-02 08:14:58 +00:00
let req = resReq . value
info " received history query " , peerId = conn . peerId , requestId = req . requestId , query = req . query
2022-01-06 11:23:25 +00:00
waku_store_queries . inc ( )
2020-08-27 02:44:09 +00:00
2022-09-02 08:14:58 +00:00
let resp = ws . findMessages ( req . query )
2020-11-24 04:53:42 +00:00
2022-09-13 16:06:23 +00:00
if not ws . wakuSwap . isNil ( ) :
2022-09-02 08:14:58 +00:00
info " handle store swap " , peerId = conn . peerId , requestId = req . requestId , text = ws . wakuSwap . text
# Perform accounting operation
# TODO: Do accounting here, response is HistoryResponse. How do we get node or swap context?
2021-10-06 12:29:08 +00:00
let peerId = conn . peerId
2022-09-02 08:14:58 +00:00
let messages = resp . messages
2021-10-06 12:29:08 +00:00
ws . wakuSwap . credit ( peerId , messages . len )
2020-11-24 04:53:42 +00:00
2022-09-02 08:14:58 +00:00
info " sending history response " , peerId = conn . peerId , requestId = req . requestId , messages = resp . messages . len
2021-04-20 01:59:14 +00:00
2022-09-02 08:14:58 +00:00
let rpc = HistoryRPC ( requestId : req . requestId , response : resp )
await conn . writeLp ( rpc . encode ( ) . buffer )
2020-08-27 02:44:09 +00:00
2021-06-09 14:37:08 +00:00
ws . handler = handler
2020-08-27 02:44:09 +00:00
ws . codec = WakuStoreCodec
2022-02-17 10:00:45 +00:00
ws . messages = StoreQueueRef . new ( capacity )
2020-09-18 13:28:19 +00:00
2022-06-13 17:59:53 +00:00
if ws . isSqliteOnly :
2022-09-13 16:06:23 +00:00
if ws . store . isNil ( ) :
warn " store not provided (nil) "
return
2022-09-16 10:55:22 +00:00
# Execute retention policy on initialization
if not ws . retentionPolicy . isNone ( ) :
let policy = ws . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( ws . store )
if resRetPolicy . isErr ( ) :
warn " an error occurred while applying the retention policy at init " , error = resRetPolicy . error ( )
2022-06-13 17:59:53 +00:00
info " SQLite-only store initialized. Messages are *not* loaded into memory. "
2022-09-15 16:13:30 +00:00
let numMessages = ws . store . getMessagesCount ( )
if numMessages . isOk ( ) :
debug " number of messages in persistent store " , messageNum = numMessages . value
waku_store_messages . set ( numMessages . value , labelValues = [ " stored " ] )
2022-06-13 17:59:53 +00:00
2022-09-13 16:06:23 +00:00
# TODO: Move this logic, together with the insert message logic
# into a "dual-store" message store implementation.
else :
if ws . store . isNil ( ) :
return
2021-11-03 10:59:51 +00:00
2022-09-16 10:55:22 +00:00
# Execute retention policy before loading any messages into in-memory store
if not ws . retentionPolicy . isNone ( ) :
let policy = ws . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( ws . store )
if resRetPolicy . isErr ( ) :
warn " an error occurred while applying the retention policy at init " , error = resRetPolicy . error ( )
2022-09-13 16:06:23 +00:00
info " loading messages from persistent storage "
2022-08-01 16:21:11 +00:00
2022-09-13 16:06:23 +00:00
let res = ws . store . getAllMessages ( )
if res . isOk ( ) :
for ( receiverTime , msg , pubsubTopic ) in res . value :
let index = Index . compute ( msg , receiverTime , pubsubTopic )
discard ws . messages . put ( index , msg , pubsubTopic )
info " successfully loaded messages from the persistent store "
else :
warn " failed to load messages from the persistent store " , err = res . error ( )
2022-08-01 16:21:11 +00:00
2022-09-15 16:13:30 +00:00
let numMessages = ws . messages . getMessagesCount ( )
if numMessages . isOk ( ) :
debug " number of messages in in-memory store " , messageNum = numMessages . value
waku_store_messages . set ( numMessages . value , labelValues = [ " stored " ] )
2022-09-13 16:07:46 +00:00
2020-11-16 08:38:52 +00:00
2022-09-16 10:55:22 +00:00
proc init * ( T : type WakuStore ,
peerManager : PeerManager ,
rng : ref rand . HmacDrbgContext ,
store : MessageStore = nil ,
wakuSwap : WakuSwap = nil ,
persistMessages = true ,
capacity = StoreDefaultCapacity ,
isSqliteOnly = false ,
retentionPolicy = none ( MessageRetentionPolicy ) ) : T =
let ws = WakuStore (
rng : rng ,
peerManager : peerManager ,
store : store ,
wakuSwap : wakuSwap ,
persistMessages : persistMessages ,
isSqliteOnly : isSqliteOnly ,
retentionPolicy : retentionPolicy
)
2022-09-02 08:14:58 +00:00
ws . init ( capacity )
return ws
2020-08-27 02:44:09 +00:00
2022-09-16 10:55:22 +00:00
2022-09-02 08:14:58 +00:00
# TODO: This should probably be an add function and append the peer to an array
2021-10-06 12:29:08 +00:00
proc setPeer * ( ws : WakuStore , peer : RemotePeerInfo ) =
2021-02-11 08:58:25 +00:00
ws . peerManager . addPeer ( peer , WakuStoreCodec )
2021-01-29 08:42:41 +00:00
waku_store_peers . inc ( )
2020-09-24 02:16:25 +00:00
2022-08-02 11:14:13 +00:00
2022-09-13 16:06:23 +00:00
proc handleMessage * ( w : WakuStore , pubsubTopic : string , msg : WakuMessage ) {. async . } =
2022-08-02 11:14:13 +00:00
if not w . persistMessages :
2021-07-13 07:18:51 +00:00
# Store is mounted but new messages should not be stored
return
2020-08-27 02:44:09 +00:00
2022-09-13 10:37:06 +00:00
if msg . ephemeral :
# The message is ephemeral, should not be stored
return
2022-09-13 14:48:33 +00:00
let insertStartTime = getTime ( ) . toUnixFloat ( )
2022-09-13 16:06:23 +00:00
let now = getNanosecondTime ( getTime ( ) . toUnixFloat ( ) )
let index = Index . compute ( msg , receivedTime = now , pubsubTopic = pubsubTopic )
2022-09-13 10:37:06 +00:00
2022-09-13 16:06:23 +00:00
trace " handling message " , topic = pubsubTopic , index = index
2022-09-13 14:48:33 +00:00
block :
if w . isSqliteOnly :
# Add messages to persistent store, if present
if w . store . isNil ( ) :
return
2022-06-13 17:59:53 +00:00
2022-09-13 14:48:33 +00:00
let resPutStore = w . store . put ( index , msg , pubsubTopic )
if resPutStore . isErr ( ) :
debug " failed to insert message to persistent store " , index = index , err = resPutStore . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-09-16 10:55:22 +00:00
return
# Execute the retention policy after insertion
if not w . retentionPolicy . isNone ( ) :
let policy = w . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( w . store )
if resRetPolicy . isErr ( ) :
debug " message retention policy failure " , error = resRetPolicy . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-06-13 17:59:53 +00:00
2022-09-15 16:13:30 +00:00
reportMessagesCountMetric ( w . store )
2022-09-13 16:06:23 +00:00
2022-09-13 14:48:33 +00:00
# TODO: Move this logic, together with the load from persistent store on init
# into a "dual-store" message store implementation.
else :
# Add message to in-memory store
let resPutInmemory = w . messages . put ( index , msg , pubsubTopic )
if resPutInmemory . isErr ( ) :
debug " failed to insert message to in-memory store " , index = index , err = resPutInmemory . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
return
2022-09-15 16:13:30 +00:00
reportMessagesCountMetric ( w . messages )
2022-09-13 14:48:33 +00:00
# Add messages to persistent store, if present
if w . store . isNil ( ) :
return
let resPutStore = w . store . put ( index , msg , pubsubTopic )
if resPutStore . isErr ( ) :
debug " failed to insert message to persistent store " , index = index , err = resPutStore . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
return
2022-09-16 10:55:22 +00:00
# Execute the retention policy after insertion
if not w . retentionPolicy . isNone ( ) :
let policy = w . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( w . store )
if resRetPolicy . isErr ( ) :
debug " message retention policy failure " , error = resRetPolicy . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-09-14 12:40:11 +00:00
let insertDuration = getTime ( ) . toUnixFloat ( ) - insertStartTime
waku_store_insert_duration_seconds . observe ( insertDuration )
2020-09-24 02:16:25 +00:00
2022-08-01 16:21:11 +00:00
# TODO: Remove after converting the query method into a non-callback method
type QueryHandlerFunc * = proc ( response : HistoryResponse ) {. gcsafe , closure . }
2022-08-02 11:14:13 +00:00
proc query ( w : WakuStore , req : HistoryQuery , peer : RemotePeerInfo ) : Future [ WakuStoreResult [ HistoryResponse ] ] {. async , gcsafe . } =
let connOpt = await w . peerManager . dialPeer ( peer , WakuStoreCodec )
if connOpt . isNone ( ) :
waku_store_errors . inc ( labelValues = [ dialFailure ] )
return err ( dialFailure )
let connection = connOpt . get ( )
let rpc = HistoryRPC ( requestId : generateRequestId ( w . rng ) , query : req )
await connection . writeLP ( rpc . encode ( ) . buffer )
var message = await connOpt . get ( ) . readLp ( MaxRpcSize . int )
let response = HistoryRPC . init ( message )
if response . isErr ( ) :
error " failed to decode response "
waku_store_errors . inc ( labelValues = [ decodeRpcFailure ] )
return err ( decodeRpcFailure )
waku_store_messages . set ( response . value . response . messages . len . int64 , labelValues = [ " retrieved " ] )
return ok ( response . value . response )
proc query * ( w : WakuStore , req : HistoryQuery ) : Future [ WakuStoreResult [ HistoryResponse ] ] {. async , gcsafe . } =
# TODO: We need to be more stratigic about which peers we dial. Right now we just set one on the service.
2020-09-24 02:16:25 +00:00
# Ideally depending on the query and our set of peers we take a subset of ideal peers.
# This will require us to check for various factors such as:
# - which topics they track
# - latency?
# - default store peer?
2021-02-11 08:58:25 +00:00
let peerOpt = w . peerManager . selectPeer ( WakuStoreCodec )
if peerOpt . isNone ( ) :
2021-02-12 08:53:52 +00:00
error " no suitable remote peers "
2022-08-01 16:21:11 +00:00
waku_store_errors . inc ( labelValues = [ peerNotFoundFailure ] )
2022-08-02 11:14:13 +00:00
return err ( peerNotFoundFailure )
2021-02-09 08:31:38 +00:00
2022-08-02 11:14:13 +00:00
return await w . query ( req , peerOpt . get ( ) )
2020-09-24 02:16:25 +00:00
2022-08-01 12:09:41 +00:00
## 21/WAKU2-FAULT-TOLERANT-STORE
2022-08-02 11:14:13 +00:00
proc queryFromWithPaging * ( w : WakuStore , query : HistoryQuery , peer : RemotePeerInfo ) : Future [ WakuStoreResult [ seq [ WakuMessage ] ] ] {. async , gcsafe . } =
## A thin wrapper for query. Sends the query to the given peer. when the query has a valid pagingInfo,
## it retrieves the historical messages in pages.
## Returns all the fetched messages, if error occurs, returns an error string
2021-05-19 19:28:09 +00:00
2022-08-02 11:14:13 +00:00
# Make a copy of the query
var req = query
2021-07-02 18:37:58 +00:00
2022-08-02 11:14:13 +00:00
var messageList : seq [ WakuMessage ] = @ [ ]
2021-05-19 19:28:09 +00:00
2022-08-02 11:14:13 +00:00
# Fetch the history in pages
while true :
let res = await w . query ( req , peer )
if res . isErr ( ) :
return err ( res . error ( ) )
2021-05-19 19:28:09 +00:00
2022-08-02 11:14:13 +00:00
let response = res . get ( )
messageList . add ( response . messages )
2021-07-02 18:37:58 +00:00
2022-08-02 11:14:13 +00:00
# Check whether it is the last page
if response . pagingInfo . pageSize = = 0 :
break
2021-07-02 18:37:58 +00:00
2022-08-02 11:14:13 +00:00
# Update paging cursor
req . pagingInfo . cursor = response . pagingInfo . cursor
2021-07-02 18:37:58 +00:00
return ok ( messageList )
2022-08-02 11:14:13 +00:00
proc queryLoop ( w : WakuStore , req : HistoryQuery , candidateList : seq [ RemotePeerInfo ] ) : Future [ WakuStoreResult [ seq [ WakuMessage ] ] ] {. async , gcsafe . } =
## Loops through the peers candidate list in order and sends the query to each
##
## Once all responses have been received, the retrieved messages are consolidated into one deduplicated list.
## if no messages have been retrieved, the returned future will resolve into a result holding an empty seq.
let queriesList = candidateList . mapIt ( w . queryFromWithPaging ( req , it ) )
2022-01-18 22:05:41 +00:00
2022-08-02 11:14:13 +00:00
await allFutures ( queriesList )
let messagesList = queriesList
2022-08-01 12:09:41 +00:00
. map ( proc ( fut : Future [ WakuStoreResult [ seq [ WakuMessage ] ] ] ) : seq [ WakuMessage ] =
2022-08-02 11:14:13 +00:00
# These futures have been awaited before using allFutures(). Call completed() just as a sanity check.
if not fut . completed ( ) or fut . read ( ) . isErr ( ) :
return @ [ ]
fut . read ( ) . value
2022-01-18 22:05:41 +00:00
)
. concat ( )
2022-08-02 11:14:13 +00:00
. deduplicate ( )
2022-01-18 22:05:41 +00:00
2022-08-02 11:14:13 +00:00
if messagesList . len = = 0 :
2022-01-18 22:05:41 +00:00
return err ( " failed to resolve the query " )
2021-05-19 19:28:09 +00:00
2022-08-02 11:14:13 +00:00
return ok ( messagesList )
proc resume * ( w : WakuStore ,
peerList : Option [ seq [ RemotePeerInfo ] ] = none ( seq [ RemotePeerInfo ] ) ,
pageSize : uint64 = DefaultPageSize ,
pubsubTopic = DefaultTopic ) : Future [ WakuStoreResult [ uint64 ] ] {. async , gcsafe . } =
2021-05-13 21:21:46 +00:00
## resume proc retrieves the history of waku messages published on the default waku pubsub topic since the last time the waku store node has been online
## 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
2022-01-18 22:05:41 +00:00
## peerList indicates the list of peers to query from.
## The history is fetched from all available peers in this list and then consolidated into one deduplicated list.
## Such candidates should be found through a discovery method (to be developed).
2021-05-26 19:33:22 +00:00
## 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.
2021-05-19 19:28:09 +00:00
## the resume proc returns the number of retrieved messages if no error occurs, otherwise returns the error string
2022-09-13 16:06:23 +00:00
# If store has not been provided, don't even try
if w . isSqliteOnly and w . store . isNil ( ) :
return err ( " store not provided " )
2022-08-02 11:14:13 +00:00
var lastSeenTime = Timestamp ( 0 )
2022-02-17 15:00:15 +00:00
var currentTime = getNanosecondTime ( epochTime ( ) )
2022-02-17 10:00:45 +00:00
2022-08-02 11:14:13 +00:00
let lastSeenItem = w . messages . last ( )
if lastSeenItem . isOk ( ) :
lastSeenTime = lastSeenItem . get ( ) . msg . timestamp
2021-05-13 21:21:46 +00:00
# adjust the time window with an offset of 20 seconds
2022-02-17 15:00:15 +00:00
let offset : Timestamp = getNanosecondTime ( 20 )
2021-05-13 21:21:46 +00:00
currentTime = currentTime + offset
lastSeenTime = max ( lastSeenTime - offset , 0 )
2021-07-02 18:37:58 +00:00
2022-08-02 11:14:13 +00:00
debug " the offline time window is " , lastSeenTime = lastSeenTime , currentTime = currentTime
2021-06-22 03:30:12 +00:00
2022-08-02 11:14:13 +00:00
let req = HistoryQuery (
pubsubTopic : pubsubTopic ,
startTime : lastSeenTime ,
endTime : currentTime ,
pagingInfo : PagingInfo (
direction : PagingDirection . FORWARD ,
pageSize : pageSize
)
)
2021-06-22 03:30:12 +00:00
2022-08-02 11:14:13 +00:00
var res : WakuStoreResult [ seq [ WakuMessage ] ]
if peerList . isSome ( ) :
2021-05-26 19:33:22 +00:00
debug " trying the candidate list to fetch the history "
2022-08-02 11:14:13 +00:00
res = await w . queryLoop ( req , peerList . get ( ) )
2021-05-19 19:28:09 +00:00
else :
2021-05-26 19:33:22 +00:00
debug " no candidate list is provided, selecting a random peer "
2021-05-19 19:28:09 +00:00
# if no peerList is set then query from one of the peers stored in the peer manager
2022-08-02 11:14:13 +00:00
let peerOpt = w . peerManager . selectPeer ( WakuStoreCodec )
2021-05-19 19:28:09 +00:00
if peerOpt . isNone ( ) :
2022-06-08 09:20:18 +00:00
warn " no suitable remote peers "
2022-08-01 16:21:11 +00:00
waku_store_errors . inc ( labelValues = [ peerNotFoundFailure ] )
2021-05-19 19:28:09 +00:00
return err ( " no suitable remote peers " )
2021-05-26 19:33:22 +00:00
debug " a peer is selected from peer manager "
2022-08-02 11:14:13 +00:00
res = await w . queryFromWithPaging ( req , peerOpt . get ( ) )
2021-05-13 21:21:46 +00:00
2022-08-02 11:14:13 +00:00
if res . isErr ( ) :
debug " failed to resume the history "
return err ( " failed to resume the history " )
2022-08-01 12:09:41 +00:00
2020-11-16 09:55:49 +00:00
2022-08-02 11:14:13 +00:00
# Save the retrieved messages in the store
var dismissed : uint = 0
var added : uint = 0
for msg in res . get ( ) :
2022-09-13 16:06:23 +00:00
let now = getNanosecondTime ( getTime ( ) . toUnixFloat ( ) )
let index = Index . compute ( msg , receivedTime = now , pubsubTopic = pubsubTopic )
if w . isSqliteOnly :
# Add messages to persistent store
let resPutStore = w . store . put ( index , msg , pubsubTopic )
if resPutStore . isErr ( ) :
debug " failed to insert message to persistent store " , index = index , err = resPutStore . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
continue
2022-09-16 10:55:22 +00:00
# Execute the retention policy after insertion
if not w . retentionPolicy . isNone ( ) :
let policy = w . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( w . store )
if resRetPolicy . isErr ( ) :
debug " message retention policy failure " , error = resRetPolicy . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-08-02 11:14:13 +00:00
2022-09-13 16:06:23 +00:00
# TODO: Move this logic, together with the load from persistent store on init
# into a "dual-store" message store implementation.
else :
# check for duplicate messages
# TODO: Should take pubsub topic into account if we are going to support topics rather than the DefaultTopic
if w . messages . contains ( index ) :
dismissed . inc ( )
continue
# Add message to in-memory store
let resPutInmemory = w . messages . put ( index , msg , pubsubTopic )
if resPutInmemory . isErr ( ) :
debug " failed to insert message to in-memory store " , index = index , err = resPutInmemory . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
continue
if w . store . isNil ( ) :
continue
# Add messages to persistent store
let resPutStore = w . store . put ( index , msg , pubsubTopic )
if resPutStore . isErr ( ) :
debug " failed to insert message to persistent store " , index = index , err = resPutStore . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-08-02 11:14:13 +00:00
continue
2022-09-16 10:55:22 +00:00
# Execute the retention policy after insertion
if not w . retentionPolicy . isNone ( ) :
let policy = w . retentionPolicy . get ( )
let resRetPolicy = policy . execute ( w . store )
if resRetPolicy . isErr ( ) :
debug " message retention policy failure " , error = resRetPolicy . error ( )
waku_store_errors . inc ( labelValues = [ insertFailure ] )
2022-08-02 11:14:13 +00:00
added . inc ( )
2022-09-13 16:06:23 +00:00
debug " resume finished successfully " , addedMessages = added , dimissedMessages = dismissed
2022-08-02 11:14:13 +00:00
2022-09-15 16:13:30 +00:00
let store : MessageStore = if w . isSqliteOnly : w . store
else : w . messages
reportMessagesCountMetric ( store )
2022-08-02 11:14:13 +00:00
return ok ( added )
2021-02-11 08:58:25 +00:00
2022-08-02 11:14:13 +00:00
## EXPERIMENTAL
# NOTE: Experimental, maybe incorporate as part of query call
proc queryWithAccounting * ( ws : WakuStore , req : HistoryQuery ) : Future [ WakuStoreResult [ HistoryResponse ] ] {. async , gcsafe . } =
let peerOpt = ws . peerManager . selectPeer ( WakuStoreCodec )
2021-02-11 08:58:25 +00:00
if peerOpt . isNone ( ) :
2021-02-12 08:53:52 +00:00
error " no suitable remote peers "
2022-08-01 16:21:11 +00:00
waku_store_errors . inc ( labelValues = [ peerNotFoundFailure ] )
2022-08-02 11:14:13 +00:00
return err ( peerNotFoundFailure )
2021-02-11 08:58:25 +00:00
2022-08-02 11:14:13 +00:00
let res = await ws . query ( req , peerOpt . get ( ) )
if res . isErr ( ) :
return err ( res . error ( ) )
2021-02-09 08:31:38 +00:00
2022-08-02 11:14:13 +00:00
let response = res . get ( )
2020-11-16 09:55:49 +00:00
2022-08-02 11:14:13 +00:00
# Perform accounting operation. Assumes wakuSwap protocol is mounted
ws . wakuSwap . debit ( peerOpt . get ( ) . peerId , response . messages . len )
2020-11-16 09:55:49 +00:00
2022-08-02 11:14:13 +00:00
return ok ( response )