Igor Sirotin 97b7601c41
refactor: build against the single liblogosdelivery library (#116)
* refactor: build against the single liblogosdelivery library

logos-delivery#4012 unifies the node lifecycle on logosdelivery_* and ships the
full API — Messaging, Reliable Channels, and the low-level Kernel (waku_*) tier
— in one liblogosdelivery library, retiring the separate libwaku. Migrate the
bindings onto it.

- internal/ffi: collapse the two bridges into one internal/ffi/liblogosdelivery
  over the single library. The kernel wrappers keep calling waku_* (kernel
  header), the lifecycle now uses logosdelivery_create_node/start_node/
  stop_node/destroy (waku_new/start/stop/destroy are gone), events use
  logosdelivery_set_event_callback. Include liblogosdelivery_kernel.h (which
  re-exports the stable header) and link -llogosdelivery. Remove
  internal/ffi/libwaku.
- pkg/kernel: repoint at internal/ffi/liblogosdelivery; the Makefile links
  -llogosdelivery.
- CI: build only `make liblogosdelivery`, headers from library/, single
  -llogosdelivery. Pinned to logos-delivery#4012 until it merges (see the
  TODO/LOGOS_DELIVERY_REF in pr.yml).

The high-level MessagingClient (pkg/messaging) lands in a follow-up PR on top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(kernel): drop removed config keys + add a runnable example

Running against the single liblogosdelivery library surfaced a config drift:
the consolidated WakuNodeConf strictly rejects unknown keys, and
common.WakuConfig still emitted `legacyStore` (removed upstream, and sent
unconditionally) and `host` (renamed to `listenAddress`). That failed node
creation, so every kernel test that starts a node failed. Reconcile the config:

- drop the LegacyStore field (and its two obsolete test references; the
  already-skipped TestCheckLegacyStore keeps compiling);
- map Host to the `listenAddress` JSON key.

Add examples/kernel: a small runnable check that drives the unified lifecycle
(logosdelivery_create_node/start/stop/destroy) and a few kernel ops (version,
listen addresses, is-online, relay subscribe/unsubscribe) over the single
library. `go run ./examples/kernel` prints OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(kernel): correct RelayPublish wire format; verify send/receive

RelayPublish marshalled the generated protobuf (content_topic), but the library
requires the WakuMessage wire format (contentTopic) and rejected it with
"Missing required field: contentTopic". Marshal the wire shape explicitly.

Turn examples/kernel into an end-to-end send/receive check: two relay nodes on
cluster 16 / shard 64, connected via the listen multiaddr, one publishes and the
other receives it off MsgChan. Verified against a locally-built liblogosdelivery:
prints "send/receive OK".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* ci: build against logos-delivery master (unpin #4012)

logos-delivery#4012 merged, so drop the LOGOS_DELIVERY_REF pin and clone the
default branch; the cache key tracks master's HEAD again.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ffi): flatten internal/ffi/liblogosdelivery into internal/ffi

With a single library there is no bridge to choose between, so the extra
subpackage adds nothing: move the bridge to internal/ffi (package ffi) and drop
the umbrella doc. pkg/kernel now imports internal/ffi and calls ffi.*.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(kernel): add a two-node relay send/receive integration test

TestRelaySendReceive connects two relay nodes, publishes a random payload from
one and requires the other to receive it — end-to-end over the single library.
Compiled by the PR gate; run under `make test` / locally against a built
liblogosdelivery. (Replaces the former examples/kernel, removed alongside the
ffi flatten.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(ffi): keep two files in package ffi, one per header

The single library still ships two headers — liblogosdelivery.h (stable
Messaging/lifecycle) and liblogosdelivery_kernel.h (low-level waku_*). Mirror
that with two files in one package instead of one merged ffi.go: keep the
messaging bridge and all the shared plumbing (Handle/RetOK/lifecycle/callbacks/
call) in liblogosdelivery.go, and keep the kernel wrappers in libwaku.go, which
reuses the shared call + logosGoCallback. This is the same two files we had,
moved into package ffi — a much smaller diff than the merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Apply suggestions from code review

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
2026-07-20 17:55:30 +01:00

317 lines
13 KiB
Go

// This file holds the low-level Kernel (waku_*) tier of the single
// liblogosdelivery library (declared in liblogosdelivery_kernel.h). The shared
// plumbing — Handle, RetOK, EventHandler, the node lifecycle, the response/event
// callbacks and the call helper — lives in liblogosdelivery.go (same package).
package ffi
/*
#include <liblogosdelivery_kernel.h>
#include <stdlib.h>
// logosGoCallback (the synchronous response callback) is defined in
// liblogosdelivery.go; the kernel wrappers below reuse it.
extern void logosGoCallback(int ret, char* msg, size_t len, void* resp);
static void cGoWakuStartDiscV5(void* ctx, void* resp) {
waku_start_discv5(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuStopDiscV5(void* ctx, void* resp) {
waku_stop_discv5(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuVersion(void* ctx, void* resp) {
waku_version(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuRelayPublish(void* ctx, const char* pubSubTopic, const char* jsonWakuMessage, int timeoutMs, void* resp) {
waku_relay_publish(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic, jsonWakuMessage, timeoutMs);
}
static void cGoWakuRelaySubscribe(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_subscribe(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuRelayAddProtectedShard(void* ctx, int clusterId, int shardId, char* publicKey, void* resp) {
waku_relay_add_protected_shard(ctx, (FFICallBack) logosGoCallback, resp, clusterId, shardId, publicKey);
}
static void cGoWakuRelayUnsubscribe(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_unsubscribe(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuConnect(void* ctx, const char* peerMultiAddr, int timeoutMs, void* resp) {
waku_connect(ctx, (FFICallBack) logosGoCallback, resp, peerMultiAddr, timeoutMs);
}
static void cGoWakuDialPeer(void* ctx, const char* peerMultiAddr, const char* protocol, int timeoutMs, void* resp) {
waku_dial_peer(ctx, (FFICallBack) logosGoCallback, resp, peerMultiAddr, protocol, timeoutMs);
}
static void cGoWakuDialPeerById(void* ctx, const char* peerId, const char* protocol, int timeoutMs, void* resp) {
waku_dial_peer_by_id(ctx, (FFICallBack) logosGoCallback, resp, peerId, protocol, timeoutMs);
}
static void cGoWakuDisconnectPeerById(void* ctx, const char* peerId, void* resp) {
waku_disconnect_peer_by_id(ctx, (FFICallBack) logosGoCallback, resp, peerId);
}
static void cGoWakuDisconnectAllPeers(void* ctx, void* resp) {
waku_disconnect_all_peers(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuListenAddresses(void* ctx, void* resp) {
waku_listen_addresses(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuGetMyENR(void* ctx, void* resp) {
waku_get_my_enr(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuGetMyPeerId(void* ctx, void* resp) {
waku_get_my_peerid(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuPingPeer(void* ctx, const char* peerAddr, int timeoutMs, void* resp) {
waku_ping_peer(ctx, (FFICallBack) logosGoCallback, resp, peerAddr, timeoutMs);
}
static void cGoWakuGetPeersInMesh(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_get_peers_in_mesh(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuGetNumPeersInMesh(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_get_num_peers_in_mesh(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuGetNumConnectedRelayPeers(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_get_num_connected_peers(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuGetConnectedRelayPeers(void* ctx, const char* pubSubTopic, void* resp) {
waku_relay_get_connected_peers(ctx, (FFICallBack) logosGoCallback, resp, pubSubTopic);
}
static void cGoWakuGetConnectedPeers(void* ctx, void* resp) {
waku_get_connected_peers(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuGetPeerIdsFromPeerStore(void* ctx, void* resp) {
waku_get_peerids_from_peerstore(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuGetConnectedPeersInfo(void* ctx, void* resp) {
waku_get_connected_peers_info(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuStoreQuery(void* ctx, const char* jsonQuery, const char* peerAddr, int timeoutMs, void* resp) {
waku_store_query(ctx, (FFICallBack) logosGoCallback, resp, jsonQuery, peerAddr, timeoutMs);
}
static void cGoWakuPeerExchangeQuery(void* ctx, uint64_t numPeers, void* resp) {
waku_peer_exchange_request(ctx, (FFICallBack) logosGoCallback, resp, numPeers);
}
static void cGoWakuGetPeerIdsByProtocol(void* ctx, const char* protocol, void* resp) {
waku_get_peerids_by_protocol(ctx, (FFICallBack) logosGoCallback, resp, protocol);
}
static void cGoWakuDnsDiscovery(void* ctx, const char* entTreeUrl, const char* nameDnsServer, int timeoutMs, void* resp) {
waku_dns_discovery(ctx, (FFICallBack) logosGoCallback, resp, entTreeUrl, nameDnsServer, timeoutMs);
}
static void cGoWakuIsOnline(void* ctx, void* resp) {
waku_is_online(ctx, (FFICallBack) logosGoCallback, resp);
}
static void cGoWakuGetMetrics(void* ctx, void* resp) {
waku_get_metrics(ctx, (FFICallBack) logosGoCallback, resp);
}
*/
import "C"
import "unsafe"
// StartDiscV5 starts DiscV5 peer discovery.
func StartDiscV5(h Handle) error {
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuStartDiscV5(h, resp) })
return err
}
// StopDiscV5 stops DiscV5 peer discovery.
func StopDiscV5(h Handle) error {
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuStopDiscV5(h, resp) })
return err
}
// Version returns the libwaku version string.
func Version(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuVersion(h, resp) })
}
// RelayPublish publishes a WakuMessage JSON on a pubsub topic and returns
// the message hash.
func RelayPublish(h Handle, pubsubTopic, messageJSON string, timeoutMs int) (string, error) {
cTopic := C.CString(pubsubTopic)
cMsg := C.CString(messageJSON)
defer C.free(unsafe.Pointer(cTopic))
defer C.free(unsafe.Pointer(cMsg))
return call(func(resp unsafe.Pointer) { C.cGoWakuRelayPublish(h, cTopic, cMsg, C.int(timeoutMs), resp) })
}
// RelaySubscribe subscribes the node to a pubsub topic.
func RelaySubscribe(h Handle, pubsubTopic string) error {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuRelaySubscribe(h, cTopic, resp) })
return err
}
// RelayAddProtectedShard registers the hex-encoded public key allowed to
// sign messages on a protected shard.
func RelayAddProtectedShard(h Handle, clusterID, shardID int, publicKeyHex string) error {
cPublicKey := C.CString(publicKeyHex)
defer C.free(unsafe.Pointer(cPublicKey))
_, err := call(func(resp unsafe.Pointer) {
C.cGoWakuRelayAddProtectedShard(h, C.int(clusterID), C.int(shardID), cPublicKey, resp)
})
return err
}
// RelayUnsubscribe unsubscribes the node from a pubsub topic.
func RelayUnsubscribe(h Handle, pubsubTopic string) error {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuRelayUnsubscribe(h, cTopic, resp) })
return err
}
// Connect dials a peer multiaddress.
func Connect(h Handle, peerMultiAddr string, timeoutMs int) error {
cAddr := C.CString(peerMultiAddr)
defer C.free(unsafe.Pointer(cAddr))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuConnect(h, cAddr, C.int(timeoutMs), resp) })
return err
}
// DialPeer dials a peer multiaddress over a specific protocol.
func DialPeer(h Handle, peerMultiAddr, protocol string, timeoutMs int) error {
cAddr := C.CString(peerMultiAddr)
cProtocol := C.CString(protocol)
defer C.free(unsafe.Pointer(cAddr))
defer C.free(unsafe.Pointer(cProtocol))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuDialPeer(h, cAddr, cProtocol, C.int(timeoutMs), resp) })
return err
}
// DialPeerByID dials a known peer id over a specific protocol.
func DialPeerByID(h Handle, peerID, protocol string, timeoutMs int) error {
cPeerID := C.CString(peerID)
cProtocol := C.CString(protocol)
defer C.free(unsafe.Pointer(cPeerID))
defer C.free(unsafe.Pointer(cProtocol))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuDialPeerById(h, cPeerID, cProtocol, C.int(timeoutMs), resp) })
return err
}
// DisconnectPeerByID drops the connection to a peer.
func DisconnectPeerByID(h Handle, peerID string) error {
cPeerID := C.CString(peerID)
defer C.free(unsafe.Pointer(cPeerID))
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuDisconnectPeerById(h, cPeerID, resp) })
return err
}
// DisconnectAllPeers drops all peer connections.
func DisconnectAllPeers(h Handle) error {
_, err := call(func(resp unsafe.Pointer) { C.cGoWakuDisconnectAllPeers(h, resp) })
return err
}
// ListenAddresses returns the node's listen multiaddresses as a
// comma-separated list.
func ListenAddresses(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuListenAddresses(h, resp) })
}
// GetMyENR returns the node's ENR record.
func GetMyENR(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetMyENR(h, resp) })
}
// GetMyPeerID returns the node's peer id.
func GetMyPeerID(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetMyPeerId(h, resp) })
}
// PingPeer pings a peer (comma-separated multiaddresses) and returns the
// round-trip time in nanoseconds.
func PingPeer(h Handle, peerAddrs string, timeoutMs int) (string, error) {
cAddr := C.CString(peerAddrs)
defer C.free(unsafe.Pointer(cAddr))
return call(func(resp unsafe.Pointer) { C.cGoWakuPingPeer(h, cAddr, C.int(timeoutMs), resp) })
}
// GetPeersInMesh returns the relay mesh peer ids for a pubsub topic as a
// comma-separated list.
func GetPeersInMesh(h Handle, pubsubTopic string) (string, error) {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
return call(func(resp unsafe.Pointer) { C.cGoWakuGetPeersInMesh(h, cTopic, resp) })
}
// GetNumPeersInMesh returns the relay mesh peer count for a pubsub topic.
func GetNumPeersInMesh(h Handle, pubsubTopic string) (string, error) {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
return call(func(resp unsafe.Pointer) { C.cGoWakuGetNumPeersInMesh(h, cTopic, resp) })
}
// GetNumConnectedRelayPeers returns the connected relay peer count for a
// pubsub topic.
func GetNumConnectedRelayPeers(h Handle, pubsubTopic string) (string, error) {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
return call(func(resp unsafe.Pointer) { C.cGoWakuGetNumConnectedRelayPeers(h, cTopic, resp) })
}
// GetConnectedRelayPeers returns the connected relay peer ids for a pubsub
// topic as a comma-separated list.
func GetConnectedRelayPeers(h Handle, pubsubTopic string) (string, error) {
cTopic := C.CString(pubsubTopic)
defer C.free(unsafe.Pointer(cTopic))
return call(func(resp unsafe.Pointer) { C.cGoWakuGetConnectedRelayPeers(h, cTopic, resp) })
}
// GetConnectedPeers returns the connected peer ids as a comma-separated
// list.
func GetConnectedPeers(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetConnectedPeers(h, resp) })
}
// GetPeerIDsFromPeerStore returns the peer-store peer ids as a
// comma-separated list.
func GetPeerIDsFromPeerStore(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetPeerIdsFromPeerStore(h, resp) })
}
// GetConnectedPeersInfo returns the connected peers' info as JSON.
func GetConnectedPeersInfo(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetConnectedPeersInfo(h, resp) })
}
// StoreQuery runs a store query (JSON) against a peer (comma-separated
// multiaddresses) and returns the response JSON.
func StoreQuery(h Handle, queryJSON, peerAddrs string, timeoutMs int) (string, error) {
cQuery := C.CString(queryJSON)
cAddr := C.CString(peerAddrs)
defer C.free(unsafe.Pointer(cQuery))
defer C.free(unsafe.Pointer(cAddr))
return call(func(resp unsafe.Pointer) { C.cGoWakuStoreQuery(h, cQuery, cAddr, C.int(timeoutMs), resp) })
}
// PeerExchangeRequest asks peer exchange for numPeers peers and returns
// the number of received peers.
func PeerExchangeRequest(h Handle, numPeers uint64) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuPeerExchangeQuery(h, C.uint64_t(numPeers), resp) })
}
// GetPeerIDsByProtocol returns the peer ids supporting a protocol as a
// comma-separated list.
func GetPeerIDsByProtocol(h Handle, protocol string) (string, error) {
cProtocol := C.CString(protocol)
defer C.free(unsafe.Pointer(cProtocol))
return call(func(resp unsafe.Pointer) { C.cGoWakuGetPeerIdsByProtocol(h, cProtocol, resp) })
}
// DnsDiscovery resolves an ENR tree URL via DNS discovery and returns the
// discovered multiaddresses as a comma-separated list.
func DnsDiscovery(h Handle, enrTreeURL, nameDNSServer string, timeoutMs int) (string, error) {
cEnrTree := C.CString(enrTreeURL)
cDNSServer := C.CString(nameDNSServer)
defer C.free(unsafe.Pointer(cEnrTree))
defer C.free(unsafe.Pointer(cDNSServer))
return call(func(resp unsafe.Pointer) { C.cGoWakuDnsDiscovery(h, cEnrTree, cDNSServer, C.int(timeoutMs), resp) })
}
// IsOnline reports the node's online state ("true"/"false").
func IsOnline(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuIsOnline(h, resp) })
}
// GetMetrics returns the node's metrics in Prometheus text format.
func GetMetrics(h Handle) (string, error) {
return call(func(resp unsafe.Pointer) { C.cGoWakuGetMetrics(h, resp) })
}