mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-06-06 22:19:30 +00:00
Combines five dep-and-build changes that all flow from the libp2p v2.0.0
upgrade and the move to the extracted libp2p_mix / mix-rln plugin stack:
waku.nimble:
* libp2p: ff8d51857 -> c43199378 (release/v2.0.0 tip; sha-pinned until
vacp2p cuts a v2.0.0 tag).
* Drop the bare `zlib < 0.2` cap — no longer needed by the upgraded
libp2p.
* websock: bare ">= 0.4.0" — replaces the d4cd68b URL+SHA workaround
that pinned through a libp2p commit-specific websock SHA.
* nim-json-rpc: switch to chaitanyaprem/nim-json-rpc#f05fad25 — relaxes
websock cap to allow >=0.4.0. TODO: revert to status-im/nim-json-rpc
once status-im/nim-json-rpc#277 merges and a tag is cut.
* lsquic: bare ">= 0.4.1" (drops URL form).
* Add mix-rln-spam-protection-plugin pin (23b278b4) and nim-libp2p-mix
pin (50c4ab4f — PR #14 HEAD); the plugin pins the same libp2p_mix
SHA so the diamond dep collapses to a single source.
waku/factory/waku.nim:
* Explicit HPService.setup(switch) / AutonatService.setup(switch)
calls. libp2p v2.0.0's Service lifecycle refactor (libp2p#2462)
removed switch.start's auto-setup loop, so any caller that assigns
directly to switch.services (we do) is responsible for calling
setup() themselves. Without it, AutonatService.addressMapper stays
nil and peerInfo.expandAddrs SIGSEGVs during start(). Wrapped in
try/except for ServiceSetupError so a setup failure surfaces as a
logged error rather than a crash.
Build / scripts:
* scripts/build_rln_mix.sh removed and Makefile simplified — librln
is now a single shared archive built from zerokit's `stateless`
features (no separate librln_mix archive).
* simulations/mixnet/build_setup.sh + setup_credentials.nim updated
to use librln_v2.0.2.a directly and run RLN keystore setup before
nodes start.
Validated:
* Cold local-cache nimble setup --localdeps -y.
* wakunode2 and chat2mix link cleanly.
* Mixnet roundtrip sim: [PASS] bob received message from alice.
* RLN proof generation + verification on every in-path mix node:
5 gen_called == 5 verified, 0 SPAM_PROOF_* errors.
309 lines
11 KiB
Nim
309 lines
11 KiB
Nim
{.push raises: [].}
|
|
|
|
import
|
|
std/[options, net],
|
|
chronos,
|
|
chronicles,
|
|
metrics,
|
|
results,
|
|
stew/byteutils,
|
|
eth/keys,
|
|
eth/p2p/discoveryv5/enr,
|
|
libp2p/crypto/crypto,
|
|
libp2p/protocols/ping,
|
|
libp2p/protocols/pubsub/gossipsub,
|
|
libp2p/protocols/pubsub/rpc/messages,
|
|
libp2p/builders,
|
|
libp2p/transports/tcptransport,
|
|
libp2p/transports/wstransport,
|
|
libp2p/utility,
|
|
brokers/broker_context
|
|
|
|
import
|
|
waku/[
|
|
waku_relay,
|
|
waku_core,
|
|
waku_core/topics/sharding,
|
|
waku_filter_v2,
|
|
waku_archive,
|
|
waku_store_sync,
|
|
waku_rln_relay,
|
|
waku_mix,
|
|
node/waku_node,
|
|
node/peer_manager,
|
|
common/option_shims,
|
|
events/message_events,
|
|
]
|
|
|
|
export waku_relay.WakuRelayHandler
|
|
|
|
declarePublicHistogram waku_histogram_message_size,
|
|
"message size histogram in kB",
|
|
buckets = [
|
|
0.0, 1.0, 3.0, 5.0, 15.0, 50.0, 75.0, 100.0, 125.0, 150.0, 500.0, 700.0, 1000.0, Inf
|
|
]
|
|
|
|
logScope:
|
|
topics = "waku node relay api"
|
|
|
|
## Waku relay
|
|
|
|
proc registerRelayHandler(
|
|
node: WakuNode, topic: PubsubTopic, appHandler: WakuRelayHandler = nil
|
|
): bool =
|
|
## Registers the only handler for the given topic.
|
|
## Notice that this handler internally calls other handlers, such as filter,
|
|
## archive, etc, plus the handler provided by the application.
|
|
## Returns `true` if a mesh subscription was created or `false` if the relay
|
|
## was already subscribed to the topic.
|
|
|
|
let alreadySubscribed = node.wakuRelay.isSubscribed(topic)
|
|
|
|
if not appHandler.isNil():
|
|
if not alreadySubscribed or not node.legacyAppHandlers.hasKey(topic):
|
|
node.legacyAppHandlers[topic] = appHandler
|
|
else:
|
|
debug "Legacy appHandler already exists for active PubsubTopic, ignoring new handler",
|
|
topic = topic
|
|
|
|
if alreadySubscribed:
|
|
return false
|
|
|
|
proc traceHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
let msgSizeKB = msg.payload.len / 1000
|
|
|
|
waku_node_messages.inc(labelValues = ["relay"])
|
|
waku_histogram_message_size.observe(msgSizeKB)
|
|
|
|
proc filterHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
if node.wakuFilter.isNil():
|
|
return
|
|
|
|
await node.wakuFilter.handleMessage(topic, msg)
|
|
|
|
proc archiveHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
if node.wakuArchive.isNil():
|
|
return
|
|
|
|
await node.wakuArchive.handleMessage(topic, msg)
|
|
|
|
proc syncHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
if node.wakuStoreReconciliation.isNil():
|
|
return
|
|
|
|
node.wakuStoreReconciliation.messageIngress(topic, msg)
|
|
|
|
proc mixHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
if node.wakuMix.isNil():
|
|
return
|
|
|
|
await node.wakuMix.handleMessage(topic, msg)
|
|
|
|
proc internalHandler(topic: PubsubTopic, msg: WakuMessage) {.async, gcsafe.} =
|
|
MessageSeenEvent.emit(node.brokerCtx, topic, msg)
|
|
|
|
let uniqueTopicHandler = proc(
|
|
topic: PubsubTopic, msg: WakuMessage
|
|
): Future[void] {.async, gcsafe.} =
|
|
await traceHandler(topic, msg)
|
|
await filterHandler(topic, msg)
|
|
await archiveHandler(topic, msg)
|
|
await syncHandler(topic, msg)
|
|
await mixHandler(topic, msg)
|
|
await internalHandler(topic, msg)
|
|
|
|
# Call the legacy (kernel API) app handler if it exists.
|
|
# Normally, hasKey is false and the MessageSeenEvent bus (new API) is used instead.
|
|
# But we need to support legacy behavior (kernel API use), hence this.
|
|
# NOTE: We can delete `legacyAppHandlers` if instead we refactor WakuRelay to support multiple
|
|
# PubsubTopic handlers, since that's actually supported by libp2p PubSub (bigger refactor...)
|
|
if node.legacyAppHandlers.hasKey(topic) and not node.legacyAppHandlers[topic].isNil():
|
|
await node.legacyAppHandlers[topic](topic, msg)
|
|
|
|
node.wakuRelay.subscribe(topic, uniqueTopicHandler)
|
|
|
|
proc getTopicOfSubscriptionEvent(
|
|
node: WakuNode, subscription: SubscriptionEvent
|
|
): Result[(PubsubTopic, Option[ContentTopic]), string] =
|
|
case subscription.kind
|
|
of ContentSub, ContentUnsub:
|
|
if node.wakuAutoSharding.isSome():
|
|
let shard = node.wakuAutoSharding.get().getShard((subscription.topic)).valueOr:
|
|
return err("Autosharding error: " & error)
|
|
return ok(($shard, some(subscription.topic)))
|
|
else:
|
|
return
|
|
err("Static sharding is used, relay subscriptions must specify a pubsub topic")
|
|
of PubsubSub, PubsubUnsub:
|
|
return ok((subscription.topic, none[ContentTopic]()))
|
|
else:
|
|
return err("Unsupported subscription type in relay getTopicOfSubscriptionEvent")
|
|
|
|
proc subscribe*(
|
|
node: WakuNode, subscription: SubscriptionEvent, handler: WakuRelayHandler
|
|
): Result[void, string] =
|
|
## Subscribes to a PubSub or Content topic. Triggers handler when receiving messages on
|
|
## this topic. WakuRelayHandler is a method that takes a topic and a Waku message.
|
|
## If `handler` is nil, the API call will subscribe to the topic in the relay mesh
|
|
## but no app handler will be registered at this time (it can be registered later with
|
|
## another call to this proc for the same gossipsub topic).
|
|
|
|
if isNil(node.wakuRelay):
|
|
error "Invalid API call to `subscribe`. WakuRelay not mounted."
|
|
return err("Invalid API call to `subscribe`. WakuRelay not mounted.")
|
|
|
|
let (pubsubTopic, contentTopicOp) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
|
|
error "Failed to decode subscription event", error = error
|
|
return err("Failed to decode subscription event: " & error)
|
|
|
|
if node.registerRelayHandler(pubsubTopic, handler):
|
|
info "subscribe", pubsubTopic, contentTopicOp
|
|
node.topicSubscriptionQueue.emit((kind: PubsubSub, topic: pubsubTopic))
|
|
else:
|
|
if isNil(handler):
|
|
warn "No-effect API call to subscribe. Already subscribed to topic", pubsubTopic
|
|
else:
|
|
info "subscribe (was already subscribed in the mesh; appHandler set)",
|
|
pubsubTopic = pubsubTopic
|
|
|
|
return ok()
|
|
|
|
proc unsubscribe*(
|
|
node: WakuNode, subscription: SubscriptionEvent
|
|
): Result[void, string] =
|
|
## Unsubscribes from a specific PubSub or Content topic.
|
|
## This will both unsubscribe from the relay mesh and remove the app handler, if any.
|
|
## NOTE: This works because using MAPI and Kernel API at the same time is unsupported.
|
|
|
|
if isNil(node.wakuRelay):
|
|
error "Invalid API call to `unsubscribe`. WakuRelay not mounted."
|
|
return err("Invalid API call to `unsubscribe`. WakuRelay not mounted.")
|
|
|
|
let (pubsubTopic, contentTopicOp) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
|
|
error "Failed to decode unsubscribe event", error = error
|
|
return err("Failed to decode unsubscribe event: " & error)
|
|
|
|
let hadHandler = node.legacyAppHandlers.hasKey(pubsubTopic)
|
|
if hadHandler:
|
|
node.legacyAppHandlers.del(pubsubTopic)
|
|
|
|
if node.wakuRelay.isSubscribed(pubsubTopic):
|
|
info "unsubscribe", pubsubTopic, contentTopicOp
|
|
node.wakuRelay.unsubscribe(pubsubTopic)
|
|
node.topicSubscriptionQueue.emit((kind: PubsubUnsub, topic: pubsubTopic))
|
|
else:
|
|
if not hadHandler:
|
|
warn "No-effect API call to `unsubscribe`. Was not subscribed", pubsubTopic
|
|
else:
|
|
info "unsubscribe (was not subscribed in the mesh; appHandler removed)",
|
|
pubsubTopic = pubsubTopic
|
|
|
|
return ok()
|
|
|
|
proc isSubscribed*(
|
|
node: WakuNode, subscription: SubscriptionEvent
|
|
): Result[bool, string] =
|
|
if node.wakuRelay.isNil():
|
|
error "Invalid API call to `isSubscribed`. WakuRelay not mounted."
|
|
return err("Invalid API call to `isSubscribed`. WakuRelay not mounted.")
|
|
|
|
let (pubsubTopic, contentTopicOp) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
|
|
error "Failed to decode subscription event", error = error
|
|
return err("Failed to decode subscription event: " & error)
|
|
|
|
return ok(node.wakuRelay.isSubscribed(pubsubTopic))
|
|
|
|
proc publish*(
|
|
node: WakuNode, pubsubTopicOp: Option[PubsubTopic], message: WakuMessage
|
|
): Future[Result[int, string]] {.async, gcsafe.} =
|
|
## Publish a `WakuMessage`. Pubsub topic contains; none, a named or static shard.
|
|
## `WakuMessage` should contain a `contentTopic` field for light node functionality.
|
|
## It is also used to determine the shard.
|
|
|
|
if node.wakuRelay.isNil():
|
|
let msg =
|
|
"Invalid API call to `publish`. WakuRelay not mounted. Try `lightpush` instead."
|
|
error "publish error", err = msg
|
|
# TODO: Improve error handling
|
|
return err(msg)
|
|
|
|
let pubsubTopic = pubsubTopicOp.valueOr:
|
|
if node.wakuAutoSharding.isNone():
|
|
return err("Pubsub topic must be specified when static sharding is enabled.")
|
|
node.wakuAutoSharding.get().getShard(message.contentTopic).valueOr:
|
|
let msg = "Autosharding error: " & error
|
|
return err(msg)
|
|
|
|
let numPeers = (await node.wakuRelay.publish(pubsubTopic, message)).valueOr:
|
|
warn "waku.relay did not publish", error = error
|
|
# Todo: If NoPeersToPublish, we might want to return ok(0) instead!!!
|
|
return err("publish failed in relay: " & $error)
|
|
|
|
notice "waku.relay published",
|
|
peerId = node.peerId,
|
|
pubsubTopic = pubsubTopic,
|
|
msg_hash = pubsubTopic.computeMessageHash(message).to0xHex(),
|
|
publishTime = getNowInNanosecondTime(),
|
|
numPeers = numPeers
|
|
|
|
# TODO: investigate if we can return error in case numPeers is 0
|
|
ok(numPeers)
|
|
|
|
proc mountRelay*(
|
|
node: WakuNode,
|
|
peerExchangeHandler = none(RoutingRecordsHandler),
|
|
maxMessageSize = int(DefaultMaxWakuMessageSize),
|
|
): Future[Result[void, string]] {.async.} =
|
|
if not node.wakuRelay.isNil():
|
|
error "wakuRelay already mounted, skipping"
|
|
return err("wakuRelay already mounted, skipping")
|
|
|
|
## The default relay topics is the union of all configured topics plus default PubsubTopic(s)
|
|
info "mounting relay protocol"
|
|
|
|
node.wakuRelay = WakuRelay.new(node.switch, maxMessageSize).valueOr:
|
|
error "failed mounting relay protocol", error = error
|
|
return err("failed mounting relay protocol: " & error)
|
|
|
|
## Add peer exchange handler
|
|
if peerExchangeHandler.isSome():
|
|
node.wakuRelay.parameters.enablePX = true
|
|
# Feature flag for peer exchange in nim-libp2p
|
|
node.wakuRelay.routingRecordsHandler.add(peerExchangeHandler.get())
|
|
|
|
if node.started:
|
|
await node.wakuRelay.start()
|
|
await node.reconnectRelayPeers()
|
|
|
|
node.switch.mount(node.wakuRelay, protocolMatcher(WakuRelayCodec))
|
|
|
|
info "relay mounted successfully"
|
|
return ok()
|
|
|
|
## Waku RLN Relay
|
|
|
|
proc mountRlnRelay*(
|
|
node: WakuNode,
|
|
rlnConf: WakuRlnConfig,
|
|
spamHandler = none(SpamHandler),
|
|
registrationHandler = none(RegistrationHandler),
|
|
) {.async.} =
|
|
info "mounting rln relay"
|
|
|
|
if node.wakuRelay.isNil():
|
|
raise newException(
|
|
CatchableError, "WakuRelay protocol is not mounted, cannot mount WakuRlnRelay"
|
|
)
|
|
|
|
let rlnRelay = (await WakuRlnRelay.new(rlnConf, registrationHandler)).valueOr:
|
|
raise newException(CatchableError, "failed to mount WakuRlnRelay: " & error)
|
|
if (rlnConf.userMessageLimit > rlnRelay.groupManager.rlnRelayMaxMessageLimit):
|
|
error "rln-relay-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract"
|
|
let validator = generateRlnValidator(rlnRelay, spamHandler)
|
|
|
|
# register rln validator as default validator
|
|
info "Registering RLN validator"
|
|
node.wakuRelay.addValidator(validator, "RLN validation failed")
|
|
|
|
node.wakuRlnRelay = rlnRelay
|