Skip to content

feat: mix poc #3284

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,7 @@
url = https://github.com/waku-org/waku-rlnv2-contract.git
ignore = untracked
branch = master
[submodule "vendor/mix"]
path = vendor/mix
url = https://github.com/vacp2p/mix/
branch = mix-waku-integ
58 changes: 58 additions & 0 deletions Dockerfile.lightpushWithMix.compile
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# BUILD NIM APP ----------------------------------------------------------------
FROM rust:1.81.0-alpine3.19 AS nim-build

ARG NIMFLAGS
ARG MAKE_TARGET=lightpushwithmix
ARG NIM_COMMIT
ARG LOG_LEVEL=TRACE

# Get build tools and required header files
RUN apk add --no-cache bash git build-base openssl-dev pcre-dev linux-headers curl jq

WORKDIR /app
COPY . .

# workaround for alpine issue: https://github.com/alpinelinux/docker-alpine/issues/383
RUN apk update && apk upgrade

# Ran separately from 'make' to avoid re-doing
RUN git submodule update --init --recursive

# Slowest build step for the sake of caching layers
RUN make -j$(nproc) deps QUICK_AND_DIRTY_COMPILER=1 ${NIM_COMMIT}

# Build the final node binary
RUN make -j$(nproc) ${NIM_COMMIT} $MAKE_TARGET LOG_LEVEL=${LOG_LEVEL} NIMFLAGS="${NIMFLAGS}"


# REFERENCE IMAGE as BASE for specialized PRODUCTION IMAGES----------------------------------------
FROM alpine:3.18 AS base_lpt

ARG MAKE_TARGET=lightpushwithmix

LABEL maintainer="[email protected]"
LABEL source="https://github.com/waku-org/nwaku"
LABEL description="Lite Push With Mix: Waku light-client"
LABEL commit="unknown"
LABEL version="unknown"

# DevP2P, LibP2P, and JSON RPC ports
EXPOSE 30303 60000 8545

# Referenced in the binary
RUN apk add --no-cache libgcc pcre-dev libpq-dev \
wget \
iproute2 \
python3 \
jq

# Fix for 'Error loading shared library libpcre.so.3: No such file or directory'
RUN ln -s /usr/lib/libpcre.so /usr/lib/libpcre.so.3

COPY --from=nim-build /app/build/lightpush_publisher_mix /usr/bin/
RUN chmod +x /usr/bin/lightpush_publisher_mix

# Standalone image to be used manually and in lpt-runner -------------------------------------------
FROM base_lpt AS standalone_lpt

ENTRYPOINT ["/usr/bin/lightpush_publisher_mix"]
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ liteprotocoltester: | build deps librln
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim liteprotocoltester $(NIM_PARAMS) waku.nims

lightpushwithmix: | build deps librln
echo -e $(BUILD_MSG) "build/$@" && \
$(ENV_SCRIPT) nim lightpushwithmix $(NIM_PARAMS) waku.nims

build/%: | build deps librln
echo -e $(BUILD_MSG) "build/$*" && \
$(ENV_SCRIPT) nim buildone $(NIM_PARAMS) waku.nims $*
Expand Down
189 changes: 189 additions & 0 deletions examples/lightpush_mix/lightpush_publisher_mix.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import
std/[tables, times, sequtils, strutils],
stew/byteutils,
chronicles,
results,
chronos,
confutils,
libp2p/crypto/crypto,
libp2p/crypto/curve25519,
libp2p/multiaddress,
eth/keys,
eth/p2p/discoveryv5/enr,
metrics,
metrics/chronos_httpserver

import mix/entry_connection, mix/protocol, mix/curve25519

import
waku/[
common/logging,
node/peer_manager,
waku_core,
waku_core/codecs,
waku_node,
waku_enr,
discovery/waku_discv5,
factory/builder,
waku_lightpush/client,
],
./lightpush_publisher_mix_config,
./lightpush_publisher_mix_metrics

const clusterId = 66
const shardId = @[0'u16]

const
LightpushPubsubTopic = PubsubTopic("/waku/2/rs/66/0")
LightpushContentTopic = ContentTopic("/examples/1/light-pubsub-mix-example/proto")

proc splitPeerIdAndAddr(maddr: string): (string, string) =
let parts = maddr.split("/p2p/")
if parts.len != 2:
error "Invalid multiaddress format", parts = parts
return

let
address = parts[0]
peerId = parts[1]
return (address, peerId)

proc setupAndPublish(rng: ref HmacDrbgContext, conf: LightPushMixConf) {.async.} =
# use notice to filter all waku messaging
setupLog(logging.LogLevel.DEBUG, logging.LogFormat.TEXT)

notice "starting publisher", wakuPort = conf.port

let
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get()
ip = parseIpAddress("0.0.0.0")
flags = CapabilitiesBitfield.init(relay = true)

let relayShards = RelayShards.init(clusterId, shardId).valueOr:
error "Relay shards initialization failed", error = error
quit(QuitFailure)

var enrBuilder = EnrBuilder.init(nodeKey)
enrBuilder.withWakuRelaySharding(relayShards).expect(
"Building ENR with relay sharding failed"
)

let record = enrBuilder.build().valueOr:
error "failed to create enr record", error = error
quit(QuitFailure)

setLogLevel(logging.LogLevel.TRACE)
var builder = WakuNodeBuilder.init()
builder.withNodeKey(nodeKey)
builder.withRecord(record)
builder.withNetworkConfigurationDetails(ip, Port(conf.port)).tryGet()

let node = builder.build().tryGet()

node.mountMetadata(clusterId).expect("failed to mount waku metadata protocol")
node.mountLightPushClient()
try:
await node.mountPeerExchange(some(uint16(clusterId)))
except CatchableError:
error "failed to mount waku peer-exchange protocol",
error = getCurrentExceptionMsg()
return

let (destPeerAddr, destPeerId) = splitPeerIdAndAddr(conf.destPeerAddr)
let (pxPeerAddr, pxPeerId) = splitPeerIdAndAddr(conf.pxAddr)
info "dest peer address", destPeerAddr = destPeerAddr, destPeerId = destPeerId
info "peer exchange address", pxPeerAddr = pxPeerAddr, pxPeerId = pxPeerId
let pxPeerInfo =
RemotePeerInfo.init(destPeerId, @[MultiAddress.init(destPeerAddr).get()])
node.peerManager.addServicePeer(pxPeerInfo, WakuPeerExchangeCodec)

let pxPeerInfo1 =
RemotePeerInfo.init(pxPeerId, @[MultiAddress.init(pxPeerAddr).get()])
node.peerManager.addServicePeer(pxPeerInfo1, WakuPeerExchangeCodec)

if not conf.mixDisabled:
let (mixPrivKey, mixPubKey) = generateKeyPair().valueOr:
error "failed to generate mix key pair", error = error
return
(await node.mountMix(clusterId, mixPrivKey)).isOkOr:
error "failed to mount waku mix protocol: ", error = $error
return

let dPeerId = PeerId.init(destPeerId).valueOr:
error "Failed to initialize PeerId", err = error
return
var conn: Connection
if not conf.mixDisabled:
conn = MixEntryConnection.newConn(
destPeerAddr, dPeerId, ProtocolType.fromString(WakuLightPushCodec), node.mix
)

await node.start()
node.peerManager.start()
node.startPeerExchangeLoop()
try:
startMetricsHttpServer("0.0.0.0", Port(8008))
except Exception:
error "failed to start metrics server: ", error = getCurrentExceptionMsg()
(await node.fetchPeerExchangePeers()).isOkOr:
warn "Cannot fetch peers from peer exchange", cause = error

if not conf.mixDisabled:
while node.getMixNodePoolSize() < conf.minMixPoolSize:
info "waiting for mix nodes to be discovered",
currentpoolSize = node.getMixNodePoolSize()
await sleepAsync(1000)
notice "publisher service started with mix node pool size ",
currentpoolSize = node.getMixNodePoolSize()

var i = 0
while i < conf.numMsgs:
if conf.mixDisabled:
let connOpt = await node.peerManager.dialPeer(dPeerId, WakuLightPushCodec)
if connOpt.isNone():
error "failed to dial peer with WakuLightPushCodec", target_peer_id = dPeerId
return
conn = connOpt.get()
i = i + 1
let text =
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam venenatis magna ut tortor faucibus, in vestibulum nibh commodo. Aenean eget vestibulum augue. Nullam suscipit urna non nunc efficitur, at iaculis nisl consequat. Mauris quis ultrices elit. Suspendisse lobortis odio vitae laoreet facilisis. Cras ornare sem felis, at vulputate magna aliquam ac. Duis quis est ultricies, euismod nulla ac, interdum dui. Maecenas sit amet est vitae enim commodo gravida. Proin vitae elit nulla. Donec tempor dolor lectus, in faucibus velit elementum quis. Donec non mauris eu nibh faucibus cursus ut egestas dolor. Aliquam venenatis ligula id velit pulvinar malesuada. Vestibulum scelerisque, justo non porta gravida, nulla justo tempor purus, at sollicitudin erat erat vel libero.
Fusce nec eros eu metus tristique aliquet. Sed ut magna sagittis, vulputate diam sit amet, aliquam magna. Aenean sollicitudin velit lacus, eu ultrices magna semper at. Integer vitae felis ligula. In a eros nec risus condimentum tincidunt fermentum sit amet ex. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam vitae justo maximus, fringilla tellus nec, rutrum purus. Etiam efficitur nisi dapibus euismod vestibulum. Phasellus at felis elementum, tristique nulla ac, consectetur neque.
Maecenas hendrerit nibh eget velit rutrum, in ornare mauris molestie. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Praesent dignissim efficitur eros, sit amet rutrum justo mattis a. Fusce mollis neque at erat placerat bibendum. Ut fringilla fringilla orci, ut fringilla metus fermentum vel. In hac habitasse platea dictumst. Donec hendrerit porttitor odio. Suspendisse ornare sollicitudin mauris, sodales pulvinar velit finibus vel. Fusce id pulvinar neque. Suspendisse eget tincidunt sapien, ac accumsan turpis.
Curabitur cursus tincidunt leo at aliquet. Nunc dapibus quam id venenatis varius. Aenean eget augue vel velit dapibus aliquam. Nulla facilisi. Curabitur cursus, turpis vel congue volutpat, tellus eros cursus lacus, eu fringilla turpis orci non ipsum. In hac habitasse platea dictumst. Nulla aliquam nisl a nunc placerat, eget dignissim felis pulvinar. Fusce sed porta mauris. Donec sodales arcu in nisl sodales, quis posuere massa ultricies. Nam feugiat massa eget felis ultricies finibus. Nunc magna nulla, interdum a elit vel, egestas efficitur urna. Ut posuere tincidunt odio in maximus. Sed at dignissim est.
Morbi accumsan elementum ligula ut fringilla. Praesent in ex metus. Phasellus urna est, tempus sit amet elementum vitae, sollicitudin vel ipsum. Fusce hendrerit eleifend dignissim. Maecenas tempor dapibus dui quis laoreet. Cras tincidunt sed ipsum sed pellentesque. Proin ut tellus nec ipsum varius interdum. Curabitur id velit ligula. Etiam sapien nulla, cursus sodales orci eu, porta lobortis nunc. Nunc at dapibus velit. Nulla et nunc vehicula, condimentum erat quis, elementum dolor. Quisque eu metus fermentum, vestibulum tellus at, sollicitudin odio. Ut vel neque justo.
Praesent porta porta velit, vel porttitor sem. Donec sagittis at nulla venenatis iaculis. Nullam vel eleifend felis. Nullam a pellentesque lectus. Aliquam tincidunt semper dui sed bibendum. Donec hendrerit, urna et cursus dictum, neque neque convallis magna, id condimentum sem urna quis massa. Fusce non quam vulputate, fermentum mauris at, malesuada ipsum. Mauris id pellentesque libero. Donec vel erat ullamcorper, dapibus quam id, imperdiet urna. Praesent sed ligula ut est pellentesque pharetra quis et diam. Ut placerat lorem eget mi fermentum aliquet.
This is message #""" &
$i & """ sent from a publisher using mix. End of transmission."""
let message = WakuMessage(
payload: toBytes(text), # content of the message
contentTopic: LightpushContentTopic, # content topic to publish to
ephemeral: true, # tell store nodes to not store it
timestamp: getNowInNanosecondTime(),
) # current timestamp

let res = await node.wakuLightpushClient.publishWithConn(
LightpushPubsubTopic, message, conn, dPeerId
)

if res.isOk():
lp_mix_success.inc()
notice "published message",
text = text,
timestamp = message.timestamp,
psTopic = LightpushPubsubTopic,
contentTopic = LightpushContentTopic
else:
error "failed to publish message", error = res.error
lp_mix_failed.inc(labelValues = ["publish_error"])

if conf.mixDisabled:
await conn.close()
await sleepAsync(conf.msgIntervalMilliseconds)
info "###########Sent all messages via mix"
quit(0)

when isMainModule:
let conf = LightPushMixConf.load()
let rng = crypto.newRng()
asyncSpawn setupAndPublish(rng, conf)
runForever()
28 changes: 28 additions & 0 deletions examples/lightpush_mix/lightpush_publisher_mix_config.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import confutils/defs

type LightPushMixConf* = object
destPeerAddr* {.desc: "Destination peer address with peerId.", name: "dp-addr".}:
string

pxAddr* {.desc: "Peer exchange address with peerId.", name: "px-addr".}: string

port* {.desc: "Port to listen on.", defaultValue: 50000, name: "port".}: int

numMsgs* {.desc: "Number of messages to send.", defaultValue: 1, name: "num-msgs".}:
int

msgIntervalMilliseconds* {.
desc: "Interval between messages in milliseconds.",
defaultValue: 1000,
name: "msg-interval"
.}: int

minMixPoolSize* {.
desc: "Number of messages to wait for before sending.",
defaultValue: 3,
name: "min-mix-pool-size"
.}: int

mixDisabled* {.
desc: "Do not use mix for publishing.", defaultValue: false, name: "without-mix"
.}: bool
8 changes: 8 additions & 0 deletions examples/lightpush_mix/lightpush_publisher_mix_metrics.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{.push raises: [].}

import metrics

declarePublicCounter lp_mix_success, "number of lightpush messages sent via mix"

declarePublicCounter lp_mix_failed,
"number of lightpush messages failed via mix", labels = ["error"]
1 change: 1 addition & 0 deletions vendor/mix
Submodule mix added at 78cca7
4 changes: 4 additions & 0 deletions waku.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ task liteprotocoltester, "Build liteprotocoltester":
let name = "liteprotocoltester"
buildBinary name, "apps/liteprotocoltester/"

task lightpushwithmix, "Build lightpushwithmix":
let name = "lightpush_publisher_mix"
buildBinary name, "examples/lightpush_mix/"

task buildone, "Build custom target":
let filepath = paramStr(paramCount())
discard buildModule filepath
Expand Down
5 changes: 3 additions & 2 deletions waku/factory/conf_builder/conf_builder.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import
./discv5_conf_builder,
./web_socket_conf_builder,
./metrics_server_conf_builder,
./rln_relay_conf_builder
./rln_relay_conf_builder,
./mix_conf_builder

export
waku_conf_builder, filter_service_conf_builder, store_sync_conf_builder,
store_service_conf_builder, rest_server_conf_builder, dns_discovery_conf_builder,
discv5_conf_builder, web_socket_conf_builder, metrics_server_conf_builder,
rln_relay_conf_builder
rln_relay_conf_builder, mix_conf_builder
38 changes: 38 additions & 0 deletions waku/factory/conf_builder/mix_conf_builder.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import chronicles, std/options, results
import libp2p/crypto/crypto, libp2p/crypto/curve25519, mix/curve25519
import ../waku_conf

logScope:
topics = "waku conf builder mix"

##################################
## Mix Config Builder ##
##################################
type MixConfBuilder* = object
enabled*: Option[bool]
mixKey*: Option[string]

proc init*(T: type MixConfBuilder): MixConfBuilder =
MixConfBuilder()

proc withEnabled*(b: var MixConfBuilder, enabled: bool) =
b.enabled = some(enabled)

proc withMixKey*(b: var MixConfBuilder, mixKey: string) =
b.mixKey = some(mixKey)

proc build*(b: MixConfBuilder): Result[Option[MixConf], string] =
if not b.enabled.get(false):
return ok(none[MixConf]())
else:
if b.mixKey.isSome():
let mixPrivKey = intoCurve25519Key(ncrutils.fromHex(b.mixKey.get()))
let mixPubKey = public(mixPrivKey)
return ok(some(MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey)))
else:
# Generate a new key pair if not provided
let keyPairResult = generateKeyPair()
if keyPairResult.isErr:
return err("Generate key pair error: " & $keyPairResult.error)
let (mixPrivKey, mixPubKey) = keyPairResult.get()
return ok(some(MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey)))
Loading
Loading