chore: Lightpush minor refactor (#3538)

* chore: refactor Lightpush (more DRY)

* chore: apply review suggestions

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>

---------

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
This commit is contained in:
Sergei Tikhomirov 2025-11-28 10:41:20 +01:00 committed by GitHub
parent c0a7debfd1
commit 1e73213a36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 114 deletions

View File

@ -163,9 +163,9 @@ proc setupAndPublish(rng: ref HmacDrbgContext, conf: LightPushMixConf) {.async.}
ephemeral: true, # tell store nodes to not store it ephemeral: true, # tell store nodes to not store it
timestamp: getNowInNanosecondTime(), timestamp: getNowInNanosecondTime(),
) # current timestamp ) # current timestamp
let res = await node.wakuLightpushClient.publishWithConn(
LightpushPubsubTopic, message, conn, dPeerId let res =
) await node.wakuLightpushClient.publish(some(LightpushPubsubTopic), message, conn)
let startTime = getNowInNanosecondTime() let startTime = getNowInNanosecondTime()

View File

@ -37,7 +37,7 @@ suite "Rate limited push service":
handlerFuture = newFuture[(string, WakuMessage)]() handlerFuture = newFuture[(string, WakuMessage)]()
let requestRes = let requestRes =
await client.publish(some(DefaultPubsubTopic), message, peer = serverPeerId) await client.publish(some(DefaultPubsubTopic), message, serverPeerId)
check await handlerFuture.withTimeout(50.millis) check await handlerFuture.withTimeout(50.millis)
@ -66,7 +66,7 @@ suite "Rate limited push service":
var endTime = Moment.now() var endTime = Moment.now()
var elapsed: Duration = (endTime - startTime) var elapsed: Duration = (endTime - startTime)
await sleepAsync(tokenPeriod - elapsed + firstWaitExtend) await sleepAsync(tokenPeriod - elapsed + firstWaitExtend)
firstWaitEXtend = 100.millis firstWaitExtend = 100.millis
## Cleanup ## Cleanup
await allFutures(clientSwitch.stop(), serverSwitch.stop()) await allFutures(clientSwitch.stop(), serverSwitch.stop())
@ -99,7 +99,7 @@ suite "Rate limited push service":
let message = fakeWakuMessage() let message = fakeWakuMessage()
handlerFuture = newFuture[(string, WakuMessage)]() handlerFuture = newFuture[(string, WakuMessage)]()
let requestRes = let requestRes =
await client.publish(some(DefaultPubsubTopic), message, peer = serverPeerId) await client.publish(some(DefaultPubsubTopic), message, serverPeerId)
discard await handlerFuture.withTimeout(10.millis) discard await handlerFuture.withTimeout(10.millis)
check: check:
@ -114,7 +114,7 @@ suite "Rate limited push service":
let message = fakeWakuMessage() let message = fakeWakuMessage()
handlerFuture = newFuture[(string, WakuMessage)]() handlerFuture = newFuture[(string, WakuMessage)]()
let requestRes = let requestRes =
await client.publish(some(DefaultPubsubTopic), message, peer = serverPeerId) await client.publish(some(DefaultPubsubTopic), message, serverPeerId)
discard await handlerFuture.withTimeout(10.millis) discard await handlerFuture.withTimeout(10.millis)
check: check:

View File

@ -210,9 +210,7 @@ proc lightpushPublishHandler(
"Waku lightpush with mix not available", "Waku lightpush with mix not available",
) )
return await node.wakuLightpushClient.publishWithConn( return await node.wakuLightpushClient.publish(some(pubsubTopic), message, conn)
pubsubTopic, message, conn, peer.peerId
)
else: else:
return await node.wakuLightpushClient.publish(some(pubsubTopic), message, peer) return await node.wakuLightpushClient.publish(some(pubsubTopic), message, peer)

View File

@ -17,8 +17,8 @@ logScope:
topics = "waku lightpush client" topics = "waku lightpush client"
type WakuLightPushClient* = ref object type WakuLightPushClient* = ref object
peerManager*: PeerManager
rng*: ref rand.HmacDrbgContext rng*: ref rand.HmacDrbgContext
peerManager*: PeerManager
publishObservers: seq[PublishObserver] publishObservers: seq[PublishObserver]
proc new*( proc new*(
@ -29,33 +29,31 @@ proc new*(
proc addPublishObserver*(wl: WakuLightPushClient, obs: PublishObserver) = proc addPublishObserver*(wl: WakuLightPushClient, obs: PublishObserver) =
wl.publishObservers.add(obs) wl.publishObservers.add(obs)
proc sendPushRequest( proc ensureTimestampSet(message: var WakuMessage) =
wl: WakuLightPushClient, if message.timestamp == 0:
req: LightPushRequest, message.timestamp = getNowInNanosecondTime()
peer: PeerId | RemotePeerInfo,
conn: Option[Connection] = none(Connection),
): Future[WakuLightPushResult] {.async.} =
let connection = conn.valueOr:
(await wl.peerManager.dialPeer(peer, WakuLightPushCodec)).valueOr:
waku_lightpush_v3_errors.inc(labelValues = [dialFailure])
return lighpushErrorResult(
LightPushErrorCode.NO_PEERS_TO_RELAY,
dialFailure & ": " & $peer & " is not accessible",
)
defer: ## Short log string for peer identifiers (overloads for convenience)
await connection.closeWithEOF() func shortPeerId(peer: PeerId): string =
shortLog(peer)
func shortPeerId(peer: RemotePeerInfo): string =
shortLog(peer.peerId)
proc sendPushRequestToConn(
wl: WakuLightPushClient, request: LightPushRequest, conn: Connection
): Future[WakuLightPushResult] {.async.} =
try: try:
await connection.writeLP(req.encode().buffer) await conn.writeLp(request.encode().buffer)
except CatchableError: except LPStreamRemoteClosedError:
error "failed to send push request", error = getCurrentExceptionMsg() error "Failed to write request to peer", error = getCurrentExceptionMsg()
return lightpushResultInternalError( return lightpushResultInternalError(
"failed to send push request: " & getCurrentExceptionMsg() "Failed to write request to peer: " & getCurrentExceptionMsg()
) )
var buffer: seq[byte] var buffer: seq[byte]
try: try:
buffer = await connection.readLp(DefaultMaxRpcSize.int) buffer = await conn.readLp(DefaultMaxRpcSize.int)
except LPStreamRemoteClosedError: except LPStreamRemoteClosedError:
error "Failed to read response from peer", error = getCurrentExceptionMsg() error "Failed to read response from peer", error = getCurrentExceptionMsg()
return lightpushResultInternalError( return lightpushResultInternalError(
@ -66,10 +64,12 @@ proc sendPushRequest(
waku_lightpush_v3_errors.inc(labelValues = [decodeRpcFailure]) waku_lightpush_v3_errors.inc(labelValues = [decodeRpcFailure])
return lightpushResultInternalError(decodeRpcFailure) return lightpushResultInternalError(decodeRpcFailure)
if response.requestId != req.requestId and let requestIdMismatch = response.requestId != request.requestId
response.statusCode != LightPushErrorCode.TOO_MANY_REQUESTS: let tooManyRequests = response.statusCode == LightPushErrorCode.TOO_MANY_REQUESTS
if requestIdMismatch and (not tooManyRequests):
# response with TOO_MANY_REQUESTS error code has no requestId by design
error "response failure, requestId mismatch", error "response failure, requestId mismatch",
requestId = req.requestId, responseRequestId = response.requestId requestId = request.requestId, responseRequestId = response.requestId
return lightpushResultInternalError("response failure, requestId mismatch") return lightpushResultInternalError("response failure, requestId mismatch")
return toPushResult(response) return toPushResult(response)
@ -78,88 +78,49 @@ proc publish*(
wl: WakuLightPushClient, wl: WakuLightPushClient,
pubSubTopic: Option[PubsubTopic] = none(PubsubTopic), pubSubTopic: Option[PubsubTopic] = none(PubsubTopic),
wakuMessage: WakuMessage, wakuMessage: WakuMessage,
peer: PeerId | RemotePeerInfo, dest: Connection | PeerId | RemotePeerInfo,
): Future[WakuLightPushResult] {.async, gcsafe.} = ): Future[WakuLightPushResult] {.async, gcsafe.} =
let conn =
when dest is Connection:
dest
else:
(await wl.peerManager.dialPeer(dest, WakuLightPushCodec)).valueOr:
waku_lightpush_v3_errors.inc(labelValues = [dialFailure])
return lighpushErrorResult(
LightPushErrorCode.NO_PEERS_TO_RELAY,
"Peer is not accessible: " & dialFailure & " - " & $dest,
)
defer:
await conn.closeWithEOF()
var message = wakuMessage var message = wakuMessage
if message.timestamp == 0: ensureTimestampSet(message)
message.timestamp = getNowInNanosecondTime()
when peer is PeerId: let msgHash = computeMessageHash(pubSubTopic.get(""), message).to0xHex()
info "publish", info "publish",
peerId = shortLog(peer), myPeerId = wl.peerManager.switch.peerInfo.peerId,
msg_hash = computeMessageHash(pubsubTopic.get(""), message).to0xHex peerId = shortPeerId(conn.peerId),
else: msgHash = msgHash,
info "publish", sentTime = getNowInNanosecondTime()
peerId = shortLog(peer.peerId),
msg_hash = computeMessageHash(pubsubTopic.get(""), message).to0xHex
let pushRequest = LightpushRequest( let request = LightpushRequest(
requestId: generateRequestId(wl.rng), pubSubTopic: pubSubTopic, message: message requestId: generateRequestId(wl.rng), pubsubTopic: pubSubTopic, message: message
) )
let publishedCount = ?await wl.sendPushRequest(pushRequest, peer) let relayPeerCount = ?await wl.sendPushRequestToConn(request, conn)
for obs in wl.publishObservers: for obs in wl.publishObservers:
obs.onMessagePublished(pubSubTopic.get(""), message) obs.onMessagePublished(pubSubTopic.get(""), message)
return lightpushSuccessResult(publishedCount) return lightpushSuccessResult(relayPeerCount)
proc publishToAny*( proc publishToAny*(
wl: WakuLightPushClient, pubSubTopic: PubsubTopic, wakuMessage: WakuMessage wl: WakuLightPushClient, pubsubTopic: PubsubTopic, wakuMessage: WakuMessage
): Future[WakuLightPushResult] {.async, gcsafe.} = ): Future[WakuLightPushResult] {.async, gcsafe.} =
## This proc is similar to the publish one but in this case # Like publish, but selects a peer automatically from the peer manager
## we don't specify a particular peer and instead we get it from peer manager
var message = wakuMessage
if message.timestamp == 0:
message.timestamp = getNowInNanosecondTime()
let peer = wl.peerManager.selectPeer(WakuLightPushCodec).valueOr: let peer = wl.peerManager.selectPeer(WakuLightPushCodec).valueOr:
# TODO: check if it is matches the situation - shall we distinguish client side missing peers from server side? # TODO: check if it is matches the situation - shall we distinguish client side missing peers from server side?
return lighpushErrorResult( return lighpushErrorResult(
LightPushErrorCode.NO_PEERS_TO_RELAY, "no suitable remote peers" LightPushErrorCode.NO_PEERS_TO_RELAY, "no suitable remote peers"
) )
return await wl.publish(some(pubsubTopic), wakuMessage, peer)
info "publishToAny",
my_peer_id = wl.peerManager.switch.peerInfo.peerId,
peer_id = peer.peerId,
msg_hash = computeMessageHash(pubsubTopic, message).to0xHex,
sentTime = getNowInNanosecondTime()
let pushRequest = LightpushRequest(
requestId: generateRequestId(wl.rng),
pubSubTopic: some(pubSubTopic),
message: message,
)
let publishedCount = ?await wl.sendPushRequest(pushRequest, peer)
for obs in wl.publishObservers:
obs.onMessagePublished(pubSubTopic, message)
return lightpushSuccessResult(publishedCount)
proc publishWithConn*(
wl: WakuLightPushClient,
pubSubTopic: PubsubTopic,
message: WakuMessage,
conn: Connection,
destPeer: PeerId,
): Future[WakuLightPushResult] {.async, gcsafe.} =
info "publishWithConn",
my_peer_id = wl.peerManager.switch.peerInfo.peerId,
peer_id = destPeer,
msg_hash = computeMessageHash(pubsubTopic, message).to0xHex,
sentTime = getNowInNanosecondTime()
let pushRequest = LightpushRequest(
requestId: generateRequestId(wl.rng),
pubSubTopic: some(pubSubTopic),
message: message,
)
#TODO: figure out how to not pass destPeer as this is just a hack
let publishedCount =
?await wl.sendPushRequest(pushRequest, destPeer, conn = some(conn))
for obs in wl.publishObservers:
obs.onMessagePublished(pubSubTopic, message)
return lightpushSuccessResult(publishedCount)

View File

@ -35,7 +35,15 @@ func isSuccess*(response: LightPushResponse): bool =
func toPushResult*(response: LightPushResponse): WakuLightPushResult = func toPushResult*(response: LightPushResponse): WakuLightPushResult =
if isSuccess(response): if isSuccess(response):
return ok(response.relayPeerCount.get(0)) let relayPeerCount = response.relayPeerCount.get(0)
return (
if (relayPeerCount == 0):
# Consider publishing to zero peers an error even if the service node
# sent us a "successful" response with zero peers
err((LightPushErrorCode.NO_PEERS_TO_RELAY, response.statusDesc))
else:
ok(relayPeerCount)
)
else: else:
return err((response.statusCode, response.statusDesc)) return err((response.statusCode, response.statusDesc))
@ -51,11 +59,6 @@ func lightpushResultBadRequest*(msg: string): WakuLightPushResult =
func lightpushResultServiceUnavailable*(msg: string): WakuLightPushResult = func lightpushResultServiceUnavailable*(msg: string): WakuLightPushResult =
return err((LightPushErrorCode.SERVICE_NOT_AVAILABLE, some(msg))) return err((LightPushErrorCode.SERVICE_NOT_AVAILABLE, some(msg)))
func lighpushErrorResult*(
statusCode: LightpushStatusCode, desc: Option[string]
): WakuLightPushResult =
return err((statusCode, desc))
func lighpushErrorResult*( func lighpushErrorResult*(
statusCode: LightpushStatusCode, desc: string statusCode: LightpushStatusCode, desc: string
): WakuLightPushResult = ): WakuLightPushResult =

View File

@ -78,9 +78,9 @@ proc handleRequest(
proc handleRequest*( proc handleRequest*(
wl: WakuLightPush, peerId: PeerId, buffer: seq[byte] wl: WakuLightPush, peerId: PeerId, buffer: seq[byte]
): Future[LightPushResponse] {.async.} = ): Future[LightPushResponse] {.async.} =
let pushRequest = LightPushRequest.decode(buffer).valueOr: let request = LightPushRequest.decode(buffer).valueOr:
let desc = decodeRpcFailure & ": " & $error let desc = decodeRpcFailure & ": " & $error
error "failed to push message", error = desc error "failed to decode Lightpush request", error = desc
let errorCode = LightPushErrorCode.BAD_REQUEST let errorCode = LightPushErrorCode.BAD_REQUEST
waku_lightpush_v3_errors.inc(labelValues = [$errorCode]) waku_lightpush_v3_errors.inc(labelValues = [$errorCode])
return LightPushResponse( return LightPushResponse(
@ -89,16 +89,16 @@ proc handleRequest*(
statusDesc: some(desc), statusDesc: some(desc),
) )
let relayPeerCount = (await handleRequest(wl, peerId, pushRequest)).valueOr: let relayPeerCount = (await wl.handleRequest(peerId, request)).valueOr:
let desc = error.desc let desc = error.desc
waku_lightpush_v3_errors.inc(labelValues = [$error.code]) waku_lightpush_v3_errors.inc(labelValues = [$error.code])
error "failed to push message", error = desc error "failed to push message", error = desc
return LightPushResponse( return LightPushResponse(
requestId: pushRequest.requestId, statusCode: error.code, statusDesc: desc requestId: request.requestId, statusCode: error.code, statusDesc: desc
) )
return LightPushResponse( return LightPushResponse(
requestId: pushRequest.requestId, requestId: request.requestId,
statusCode: LightPushSuccessCode.SUCCESS, statusCode: LightPushSuccessCode.SUCCESS,
statusDesc: none[string](), statusDesc: none[string](),
relayPeerCount: some(relayPeerCount), relayPeerCount: some(relayPeerCount),
@ -123,7 +123,7 @@ proc initProtocolHandler(wl: WakuLightPush) =
) )
try: try:
rpc = await handleRequest(wl, conn.peerId, buffer) rpc = await wl.handleRequest(conn.peerId, buffer)
except CatchableError: except CatchableError:
error "lightpush failed handleRequest", error = getCurrentExceptionMsg() error "lightpush failed handleRequest", error = getCurrentExceptionMsg()
do: do: