mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-23 05:00:21 +00:00
* Reshape per-layer API into api/ folders and thin the FFI over them
Each layer now separates its constructible core from its public surface:
- core module (waku.nim / messaging_client.nim /
reliable_channel_manager.nim): the type plus new/start/stop and the
private construction helpers.
- api/ folder: one module per differentiated set of operations
(waku: topics/relay/filter/lightpush/store/peer_manager/discovery/
debug/health) plus an events surface.
The waku api is reshaped to be the complete operation surface the C
bindings need, so the library no longer reaches into node internals:
relayPublish returns the message hash, relaySubscribe takes an optional
handler, filter/lightpush auto-select the service peer, connectedPeersInfo
returns structured data, pingPeer honours the timeout, plus
relayNumPeersInMesh / relayNumConnectedPeers / isOnline. library/ is now a
thin C-ABI shim: each {.ffi.} proc only marshals cstring/JSON/callbacks and
delegates to ctx.myLib[].waku.<op> (or messagingClient.<op>).
app_callbacks re-exports the modules defining its handler types, which the
included FFI files previously relied on by leakage.
Events move next to the surface that owns them, with each dependency kept
pointing the right way:
- waku/events/ relocated under waku/api/events/.
- channel events live in channels/api/events.nim.
- the four messaging-level message events move to messaging/api/events;
MessageSeenEvent stays in waku because it is emitted by waku core, so
moving it would make waku depend on the messaging layer.
- delivery_events renamed to filter_subscribe_events to match the
OnFilterSubscribe/Unsubscribe events it actually declares.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add reliable-channel FFI ops + events (nim-ffi v0.1.3)
Expose the reliable-channel layer through the v0.1.3 FFI:
- channel_create / channel_send / channel_close call the
ReliableChannelManager api (createReliableChannel / send / closeChannel),
marshalling channel id + base64 payload + ephemeral by hand
- channel message received / sent / errored are surfaced by listening to the
channel-layer broker events in start_node and forwarding them through
callEventCallback (received payload base64-encoded), dropped in stop_node
Stays on nim-ffi v0.1.3 (no typed/CBOR rewrite).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Expose reliable-channel ops in the stable C header (#3851)
The library already ships as a single .so with a tiered header surface
(liblogosdelivery.h = stable Messaging/Reliable-Channels, liblogosdelivery_kernel.h
= advanced Kernel). Per that tiering, the reliable-channel ops belong on the
stable surface, so declare channel_create / channel_send / channel_close in
liblogosdelivery.h and document the channel lifecycle events delivered through
the event callback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Graft PR#3975 interface layer onto decomposed foundation (events deduped)
Add IKernel/IMessagingClient/IReliableChannelManager/ILogosDelivery interface
classes under logos_delivery/api/. The EventBroker types PR#3975 hoisted into
these files already exist in PR#3989's decomposed */api/events/ modules, so the
interface files re-export those modules instead of redefining the types
(avoids 8 duplicate EventBroker definitions). api/types.nim kept at the
foundation version (ChannelId stays in channels/types.nim, which the decomposed
modules import).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Wire impl classes to interfaces (inherit; relocate SendHandler)
- Waku : IKernel, MessagingClient : IMessagingClient,
ReliableChannelManager : IReliableChannelManager.
- The operation procs already live in PR#3989's decomposed */api/ modules and
stay as plain procs (nothing dispatches through the interface types, so no
method-ization is needed).
- SendHandler now lives in reliable_channel_manager_api.nim (its PR#3975 home);
removed the duplicate from reliable_channel.nim, which re-exports the
interface module so channels/api/{channel_lifecycle,send} still see it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Wire LogosDelivery to ILogosDelivery orchestrator interface
LogosDelivery : ILogosDelivery; start/stop/isOnline become method overrides.
Peripheral PR#3975 edits (lightpush/store clients, self_req_handlers,
statistics) are import-reorg artifacts of deleting waku/utils/requests.nim,
which the decomposed structure keeps -- so they are intentionally not ported.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Dedup EventConnectionStatusChange (re-export from health_events)
9th duplicate EventBroker type: defined in both logos_delivery_api.nim and the
decomposed waku/api/events/health_events.nim. The interface file now re-exports
it. liblogosdelivery builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Move events back into interface-class source files (restore #3975 placement)
Reverses the earlier dedup-by-re-export: event TYPE definitions now live in the
interface classes, and the emptied decomposed event files are removed.
- MessageSeenEvent -> logos_delivery/api/kernel_api.nim
- Message{Sent,Error,Propagated,Received}Event -> api/messaging_client_api.nim
- ChannelMessage{Received,Sent,Error}Event -> api/reliable_channel_manager_api.nim
- EventConnectionStatusChange -> api/logos_delivery_api.nim
Deleted (became empty after the move):
- logos_delivery/waku/api/events/message_events.nim
- logos_delivery/messaging/api/events.nim
- logos_delivery/channels/api/events.nim
health_events.nim keeps its two remaining events (content/shard topic health).
Rewiring: each layer re-exports its interface module (waku->kernel_api,
messaging_client->messaging_client_api, reliable_channel->reliable_channel_manager_api,
which also re-exports messaging_client_api). Deep emitters/listeners
(subscription_manager, waku_node, waku_node/relay, node_health_monitor,
recv_service, send_service) import the owning interface module directly.
kernel_api stays below node level (types/topics/message/store-common) so the
node->kernel_api imports are acyclic. liblogosdelivery builds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* nph formatting
---------
Co-authored-by: Ivan FB <ivansete@status.im>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
265 lines
9.2 KiB
Nim
265 lines
9.2 KiB
Nim
import logos_delivery/waku/compat/option_valueor
|
|
{.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/pubsub,
|
|
libp2p/protocols/pubsub/rpc/messages,
|
|
libp2p/builders,
|
|
libp2p/transports/tcptransport,
|
|
libp2p/transports/wstransport,
|
|
libp2p/utility,
|
|
brokers/broker_context
|
|
|
|
import
|
|
logos_delivery/waku/[
|
|
waku_relay,
|
|
waku_core,
|
|
waku_core/topics/sharding,
|
|
waku_filter_v2,
|
|
waku_archive,
|
|
waku_store_sync,
|
|
rln,
|
|
node/waku_node,
|
|
node/subscription_manager,
|
|
node/peer_manager,
|
|
]
|
|
import logos_delivery/api/kernel_api # MessageSeenEvent
|
|
|
|
export waku_relay.WakuRelayHandler
|
|
|
|
logScope:
|
|
topics = "waku node relay api"
|
|
|
|
## Waku relay
|
|
|
|
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, _) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
|
|
error "Failed to decode subscription event", error = error
|
|
return err("Failed to decode subscription event: " & error)
|
|
|
|
# strict version
|
|
#if contentTopicOp.isSome():
|
|
# return
|
|
# node.subscriptionManager.subscribe(pubsubTopic, contentTopicOp.get(), handler)
|
|
return node.subscriptionManager.subscribeShard(pubsubTopic, handler)
|
|
|
|
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, _) = getTopicOfSubscriptionEvent(node, subscription).valueOr:
|
|
error "Failed to decode unsubscribe event", error = error
|
|
return err("Failed to decode unsubscribe event: " & error)
|
|
|
|
# strict version
|
|
#if contentTopicOp.isSome():
|
|
# return node.subscriptionManager.unsubscribe(pubsubTopic, contentTopicOp.get())
|
|
return node.subscriptionManager.unsubscribeAll(pubsubTopic)
|
|
|
|
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 setRlnValidator*(
|
|
node: WakuNode,
|
|
rlnConf: WakuRlnConfig,
|
|
spamHandler = none(SpamHandler),
|
|
registrationHandler = none(RegistrationHandler),
|
|
) {.async.} =
|
|
info "setting rln validator"
|
|
|
|
let rln = (await Rln.new(rlnConf, registrationHandler)).valueOr:
|
|
raise newException(CatchableError, "failed to set rln validator: " & error)
|
|
if (rlnConf.userMessageLimit > rln.groupManager.rlnRelayMaxMessageLimit):
|
|
error "rln-user-message-limit can't exceed the MAX_MESSAGE_LIMIT in the rln contract"
|
|
|
|
node.rln = rln
|
|
|
|
if node.wakuRelay.isNil():
|
|
info "WakuRelay not mounted; RLN validator not set"
|
|
return
|
|
|
|
## Bridges RLN's protocol-agnostic message validation into a relay
|
|
## (gossipsub) validator. The core decision is made by
|
|
## `validateMessageAndUpdateLog`; this maps the result to
|
|
## `pubsub.ValidationResult` so the validator can be installed on
|
|
## WakuRelay's validator chain.
|
|
proc validator(
|
|
topic: string, message: WakuMessage
|
|
): Future[pubsub.ValidationResult] {.async.} =
|
|
trace "rln-relay topic validator is called"
|
|
rln.clearNullifierLog()
|
|
|
|
let msgProof = RateLimitProof.init(message.proof).valueOr:
|
|
trace "rln validator reject", error = error
|
|
return pubsub.ValidationResult.Reject
|
|
|
|
# validate the message and update log
|
|
let validationRes = await rln.validateMessageAndUpdateLog(message)
|
|
|
|
let
|
|
proof = byteutils.toHex(msgProof.proof)
|
|
root = inHex(msgProof.merkleRoot)
|
|
shareX = inHex(msgProof.shareX)
|
|
shareY = inHex(msgProof.shareY)
|
|
nullifier = inHex(msgProof.nullifier)
|
|
|
|
case validationRes
|
|
of Valid:
|
|
trace "message validity is verified, relaying",
|
|
proof = proof,
|
|
root = root,
|
|
shareX = shareX,
|
|
shareY = shareY,
|
|
nullifier = nullifier
|
|
waku_rln_valid_messages_total.inc(labelValues = [topic])
|
|
return pubsub.ValidationResult.Accept
|
|
of Invalid:
|
|
trace "message validity could not be verified, discarding",
|
|
proof = proof,
|
|
root = root,
|
|
shareX = shareX,
|
|
shareY = shareY,
|
|
nullifier = nullifier
|
|
return pubsub.ValidationResult.Reject
|
|
of Spam:
|
|
trace "A spam message is found! yay! discarding:",
|
|
proof = proof,
|
|
root = root,
|
|
shareX = shareX,
|
|
shareY = shareY,
|
|
nullifier = nullifier
|
|
if spamHandler.isSome():
|
|
let handler = spamHandler.get()
|
|
handler(message)
|
|
return pubsub.ValidationResult.Reject
|
|
|
|
# register rln validator as default validator
|
|
info "Registering RLN validator"
|
|
node.wakuRelay.addValidator(validator, "RLN validation failed")
|