mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-25 05:53:11 +00:00
Merge remote-tracking branch 'origin/master' into e2e-migration-docker-subset
# Conflicts: # .github/workflows/ci.yml # .github/workflows/e2e-api-tests.yml
This commit is contained in:
commit
80550e8429
551
.claude/skills/nim-brokers/SKILL.md
Normal file
551
.claude/skills/nim-brokers/SKILL.md
Normal file
@ -0,0 +1,551 @@
|
||||
---
|
||||
name: nim-brokers
|
||||
description: Reference for writing code against the nim-brokers (`brokers`) nimble package — EventBroker, RequestBroker, MultiRequestBroker, SignalBroker, body sugars (listenIt, provideIt, reprovideIt, onSignalIt), bind* sugar, BrokerContext scoping, (mt) multi-thread variants, the (API) FFI shared-library surface, and BrokerInterface/BrokerImplement. Use when declaring brokers, registering listeners/providers/handlers, emitting or requesting, or exposing brokers to C/C++/Python/Rust/Go.
|
||||
---
|
||||
|
||||
# Working with `nim-brokers`
|
||||
|
||||
> Agent skill for any project that depends on the `brokers` nimble package.
|
||||
> For auto-discovery, copy this file into the consuming project as
|
||||
> `.claude/skills/nim-brokers/SKILL.md` (or reference it from CLAUDE.md/AGENTS.md).
|
||||
> Type-safe, decoupled messaging on top of **chronos** + **results**.
|
||||
> All public APIs are exception-free: errors ride `Result[T, string]`, never raises.
|
||||
|
||||
## Mental model
|
||||
|
||||
Four macros, each declares a **broker type** and generates its full API. The
|
||||
type *is* the channel — you call class-method-style on the typedesc: `T.emit`,
|
||||
`T.request`, `T.listen`, `T.setProvider`. No instances, no singletons to wire.
|
||||
|
||||
| Macro | Pattern | Producer side | Consumer side |
|
||||
|-------|---------|---------------|---------------|
|
||||
| `EventBroker` | pub/sub, many→many, fire-and-forget | `T.emit(...)` | `T.listen(handler)` |
|
||||
| `RequestBroker` | request/response, **single** provider | `T.setProvider(handler)` | `T.request(...)` |
|
||||
| `MultiRequestBroker` | request/response, **many** providers, fan-out | `T.setProvider(handler)` (N×) | `T.request(...)` |
|
||||
| `SignalBroker` | one-way notification, **single** handler, no reply | `T.signal(...)` | `T.onSignal(handler)` |
|
||||
|
||||
`(mt)` suffix → multi-thread variant (cross-thread dispatch). `(sync)` on
|
||||
RequestBroker → blocking, non-async. `(API)` → FFI shared-library surface.
|
||||
|
||||
Any provider/handler that is a **class method** (`self.foo`) can be installed
|
||||
with the `bind*` / `rebind*` sugar instead of a hand-written forwarding closure —
|
||||
see "Binding class-method providers" below. For inline bodies, each registration
|
||||
verb also has a **body-sugar form** (`listenIt`, `provideIt` / `reprovideIt`,
|
||||
`onSignalIt`) that takes a block instead of a lambda — see the per-broker sections.
|
||||
|
||||
Import only what you use:
|
||||
```nim
|
||||
import brokers/event_broker
|
||||
import brokers/request_broker
|
||||
import brokers/multi_request_broker
|
||||
import brokers/signal_broker
|
||||
import brokers/broker_context # only if you need explicit contexts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## EventBroker — pub/sub
|
||||
|
||||
```nim
|
||||
import chronos, brokers/event_broker
|
||||
|
||||
EventBroker:
|
||||
type UserLoggedIn = object
|
||||
userId*: int
|
||||
name*: string
|
||||
|
||||
# listen returns Result[ListenerHandle, string]; keep the handle to drop later
|
||||
let h = UserLoggedIn.listen(
|
||||
proc(evt: UserLoggedIn): Future[void] {.async: (raises: []).} =
|
||||
info "login", id = evt.userId
|
||||
)
|
||||
|
||||
UserLoggedIn.emit(UserLoggedIn(userId: 7, name: "zoli")) # by value
|
||||
UserLoggedIn.emit(userId = 7, name = "zoli") # by fields (inline-object only)
|
||||
|
||||
await UserLoggedIn.dropListener(h.get()) # drop one — cancels its in-flight work
|
||||
await UserLoggedIn.dropAllListeners() # drop all for this context
|
||||
```
|
||||
|
||||
- `emit` is **sync `void`** in *every* lane (single-thread, `(mt)`, `(API)`):
|
||||
snapshots listeners, `asyncSpawn`s each. It does not await delivery —
|
||||
`await sleepAsync(0)` or yield to flush in tests. Never `await`/`waitFor` an emit.
|
||||
- Handlers MUST be `{.async: (raises: []).}`. Swallow your own exceptions.
|
||||
- `dropListener`/`dropAllListeners` are **`async` (`Future[void]`) in every lane**
|
||||
— `await` them (or `discard`/`waitFor` in sync/`{.thread.}` contexts). Single-thread
|
||||
cancels in-flight handlers before returning; MT/API bodies are suspension-free.
|
||||
|
||||
### `listenIt` — listener body sugar (all lanes)
|
||||
```nim
|
||||
# The block IS the listener body; the event value is injected as `it`.
|
||||
# Returns listen's Result[<T>Listener, string] — keep it to drop later.
|
||||
let h2 = UserLoggedIn.listenIt:
|
||||
echo "login: ", it.name, " (#", it.userId, ")"
|
||||
|
||||
let h3 = UserLoggedIn.listenIt(myCtx): # ctx-scoped form; body may await
|
||||
await sleepAsync(chronos.milliseconds(1))
|
||||
echo "scoped: ", it.name
|
||||
```
|
||||
- `raises: []` is enforced as for a hand-written listener; `void` event types
|
||||
inject nothing (the block is just the body).
|
||||
|
||||
### Payload variants
|
||||
```nim
|
||||
EventBroker:
|
||||
type Tick = void # payload-less signal: Tick.emit() / listen(proc(): Future[void]...)
|
||||
EventBroker:
|
||||
type Score = int # native/alias/external types auto-wrapped in distinct
|
||||
EventBroker:
|
||||
type Blob = ref object # ref payloads fine
|
||||
data*: seq[byte]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RequestBroker — single provider request/response
|
||||
|
||||
Two declaration styles. **Coupled** (named `type` + `proc`) and **proc-sugar**
|
||||
(payload decoupled, broker named after the Capitalized verb).
|
||||
|
||||
```nim
|
||||
import chronos, brokers/request_broker
|
||||
|
||||
# Coupled: broker name == type name == request() return payload
|
||||
RequestBroker:
|
||||
type FetchUser = object
|
||||
name*: string
|
||||
proc signature*(id: int): Future[Result[FetchUser, string]] {.async.}
|
||||
|
||||
FetchUser.setProvider(
|
||||
proc(id: int): Future[Result[FetchUser, string]] {.async.} =
|
||||
ok(FetchUser(name: "u" & $id))
|
||||
).isOk()
|
||||
|
||||
let r = await FetchUser.request(42) # Result[FetchUser, string]
|
||||
FetchUser.clearProvider()
|
||||
```
|
||||
|
||||
```nim
|
||||
# Proc-sugar: broker = Capitalized verb, request() returns the RAW payload
|
||||
RequestBroker:
|
||||
proc getVersion(): Future[Result[string, string]] {.async.} # -> broker `GetVersion`
|
||||
|
||||
GetVersion.setProvider(
|
||||
proc(): Future[Result[string, string]] {.async.} = ok("1.2.3")).get()
|
||||
let v = await GetVersion.request() # r.value is plain string, no unwrap
|
||||
```
|
||||
|
||||
Rules & behaviors:
|
||||
- **One provider per signature.** A second `setProvider` returns `err(...)` (no
|
||||
silent override). `clearProvider()` first to swap.
|
||||
- Two signature slots coexist: zero-arg and arg-based (overload by arity).
|
||||
- Provider exceptions are caught → `err(<msg>)`. Unset provider → `err(...)`.
|
||||
- `isProvided()` checks registration. `T.request` is `async` here.
|
||||
|
||||
### `provideIt` / `reprovideIt` — provider body sugar (all lanes)
|
||||
```nim
|
||||
# The block is the provider's REAL proc body — the declared signature arg
|
||||
# names (`id` here) are injected; return / result= / trailing expression work.
|
||||
discard FetchUser.provideIt: # -> setProvider (keeps "already set" guard)
|
||||
if id < 0:
|
||||
return err("bad id: " & $id)
|
||||
return ok(FetchUser(name: "u" & $id))
|
||||
|
||||
discard FetchUser.reprovideIt: # -> replaceProvider (swap, no guard)
|
||||
ok(FetchUser(name: "v2-u" & $id)) # trailing expression works too
|
||||
```
|
||||
- A body that could fall through without producing a value is a **compile
|
||||
error** (it would silently answer `err("")`) — e.g. a bare `echo` body, or an
|
||||
`if` without `else` where only one branch returns.
|
||||
- Dual-slot brokers name the slots explicitly: `provideIt` / `reprovideIt` for
|
||||
the args slot, `provideItNoArgs` / `reprovideItNoArgs` for the zero-arg slot.
|
||||
- Sync mode: same sugar, body cannot `await`. Ctx-form: `T.provideIt(ctx): body`.
|
||||
|
||||
### Sync mode — no event loop needed
|
||||
```nim
|
||||
RequestBroker(sync):
|
||||
proc getId(): Result[int, string] # note: no Future, no {.async.}
|
||||
GetId.setProvider(proc(): Result[int, string] = ok(42)).isOk()
|
||||
let id = GetId.request() # blocking, returns Result directly
|
||||
```
|
||||
|
||||
### void payload (action with no return value)
|
||||
```nim
|
||||
RequestBroker:
|
||||
proc doReset(force: bool): Future[Result[void, string]] {.async.}
|
||||
DoReset.setProvider(proc(force: bool): Future[Result[void, string]] {.async.} =
|
||||
if force: ok() else: err("need force")).isOk()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MultiRequestBroker — fan-out to many providers
|
||||
|
||||
Async only. `request()` calls **all** providers via `allFinished`, returns
|
||||
`Result[seq[Payload], string]`. Any provider failing fails the whole request.
|
||||
|
||||
```nim
|
||||
import chronos, brokers/multi_request_broker
|
||||
|
||||
MultiRequestBroker:
|
||||
type Quote = object
|
||||
price*: int
|
||||
proc signature*(sym: string): Future[Result[Quote, string]] {.async.}
|
||||
|
||||
discard Quote.setProvider(proc(sym: string): Future[Result[Quote, string]] {.async.} =
|
||||
ok(Quote(price: 100)))
|
||||
discard Quote.setProvider(proc(sym: string): Future[Result[Quote, string]] {.async.} =
|
||||
ok(Quote(price: 101)))
|
||||
|
||||
let all = await Quote.request("BTC") # all.get() is seq[Quote], len == 2
|
||||
Quote.removeProvider(handle.get()) # remove one (handle from setProvider)
|
||||
Quote.clearProviders() # remove all
|
||||
```
|
||||
|
||||
- No providers registered → `ok(@[])` (empty, not error).
|
||||
- Identical handler refs deduplicated on registration.
|
||||
- `setProvider` returns `Result[ProviderHandle, string]`; capture it for `removeProvider`.
|
||||
|
||||
### `provideIt` — provider body sugar (adds, not replaces)
|
||||
```nim
|
||||
# Same body sugar as RequestBroker, but every provideIt ADDS a provider —
|
||||
# there is NO reprovideIt (no replace verb), and the generated closure is a
|
||||
# fresh reference, so it never dedups. Returns setProvider's handle.
|
||||
let h1 = Quote.provideIt:
|
||||
ok(Quote(price: 100))
|
||||
let h2 = Quote.provideIt: # a second provider — NOT a replacement
|
||||
if sym.len == 0:
|
||||
return err("empty symbol")
|
||||
return ok(Quote(price: 101))
|
||||
|
||||
Quote.removeProvider(h1.get()) # drop just one by its handle
|
||||
```
|
||||
- Dual-slot brokers: `provideIt` = args slot, `provideItNoArgs` = zero-arg slot.
|
||||
- Same fall-through compile check as RequestBroker's `provideIt`.
|
||||
|
||||
---
|
||||
|
||||
## SignalBroker — one-way notification, single handler
|
||||
|
||||
Fire-and-forget into a module: an **inverted EventBroker** (single handler, no
|
||||
reply path). `signal()` is a **plain (non-async) proc** returning
|
||||
`Result[void, string]`; it does not tell you whether the handler *succeeded* —
|
||||
only whether it was **accepted**. Handler exceptions are swallowed (chronicles
|
||||
`warn`). For delivery confirmation, use `RequestBroker` with a `void` response.
|
||||
|
||||
```nim
|
||||
import chronos, brokers/signal_broker
|
||||
|
||||
SignalBroker:
|
||||
type IngestSample = object
|
||||
deviceId*: string
|
||||
value*: float64
|
||||
|
||||
# ONE handler (a second onSignal returns err). Handler is async, raises: [].
|
||||
discard IngestSample.onSignal(
|
||||
proc(s: IngestSample) {.async: (raises: []).} =
|
||||
info "sample", dev = s.deviceId, v = s.value)
|
||||
|
||||
let r = IngestSample.signal(IngestSample(deviceId: "d1", value: 0.5)) # by value
|
||||
discard IngestSample.signal(deviceId = "d2", value = 1.25) # by fields
|
||||
# r: Result[void, string]
|
||||
# ok() = ACCEPTED (a handler exists + queue had room) — NOT "handled"
|
||||
# err() = "no signal handler installed" | "queue full"
|
||||
|
||||
await IngestSample.dropSignalHandler() # async Future[void] — await it
|
||||
```
|
||||
|
||||
### `onSignalIt` — handler body sugar (all lanes)
|
||||
```nim
|
||||
# Same as listenIt, for the single signal handler: the block is the handler
|
||||
# body, the signal value is injected as `it`. onSignal's duplicate guard and
|
||||
# Result return are unchanged.
|
||||
discard IngestSample.onSignalIt:
|
||||
echo it.deviceId, " = ", it.value
|
||||
|
||||
discard Wakeup.onSignalIt: echo "tick" # void payload: nothing injected
|
||||
```
|
||||
|
||||
- `signal()` is **sync**, never `await` it. `dropSignalHandler()` is **async**.
|
||||
- `type Foo = void` → payload-less pulse: `Foo.signal()` / `onSignal(proc() ...)`.
|
||||
- Mock/replace trio (owning-thread only on `(mt)`): `replaceSignalHandler`,
|
||||
`getCurrentSignalHandler`, `withMockSignalHandler(ctx, mock): body`.
|
||||
- `(mt)` and `(API)` variants mirror the other brokers (one handler per context).
|
||||
|
||||
---
|
||||
|
||||
## Binding class-method providers — `bind*` / `rebind*` (v3.1)
|
||||
|
||||
Nim has no bound-method values (`self.send` is not a closure), so installing a
|
||||
class method as a provider/handler normally needs a hand-written trampoline. The
|
||||
`bind*` sugar synthesises it — **identical codegen, identical `self` capture**.
|
||||
Passing a plain closure works too, so it is a strict superset of the typed verbs
|
||||
(`setProvider` / `listen` / `onSignal` stay untouched).
|
||||
|
||||
```nim
|
||||
# before — hand-written forwarding closure
|
||||
MessagingSend.setProvider(self.brokerCtx,
|
||||
proc(e: MessageEnvelope): Future[Result[RequestId, string]] {.async.} =
|
||||
await self.send(e))
|
||||
|
||||
# after — the sugar generates exactly that trampoline
|
||||
MessagingSend.bindProvider(self.brokerCtx, self.send)
|
||||
```
|
||||
|
||||
| Broker | install sugar | replace sugar |
|
||||
|--------|---------------|---------------|
|
||||
| `RequestBroker` / `(mt)` | `bindProvider` | `rebindProvider` |
|
||||
| `MultiRequestBroker` | `bindProvider` (additive) | — |
|
||||
| `EventBroker` / `(mt)` | `bindListener` (returns the listen handle) | — |
|
||||
| `SignalBroker` / `(mt)` | `bindSignalHandler` | `rebindSignalHandler` |
|
||||
|
||||
- Each verb has a **ctx-form** (`bindProvider(ctx, m)`) and a **no-ctx-form**
|
||||
(`bindProvider(m)` → thread-global context).
|
||||
- Dual-slot RequestBrokers (zero-arg **and** arg signatures) disambiguate by
|
||||
arity automatically.
|
||||
- Works on the `(API)` lane too — usable inside `setupProviders(ctx)`.
|
||||
|
||||
---
|
||||
|
||||
## BrokerContext — scoping / multi-instance
|
||||
|
||||
Every API takes an **optional first `BrokerContext` arg**. Omit it → the
|
||||
thread-global context (`DefaultBrokerContext`). Use contexts to run independent
|
||||
broker instances (per component, per test, per thread).
|
||||
|
||||
```nim
|
||||
import brokers/broker_context
|
||||
|
||||
let ctx = NewBrokerContext() # globally-unique id (atomic)
|
||||
|
||||
discard MyEvent.listen(ctx, handler)
|
||||
MyEvent.emit(ctx, payload)
|
||||
FetchUser.setProvider(ctx, provider)
|
||||
let r = await FetchUser.request(ctx, 42)
|
||||
await MyEvent.dropAllListeners(ctx)
|
||||
```
|
||||
|
||||
Thread setup helpers (callable before the event loop starts):
|
||||
| Call | Use |
|
||||
|------|-----|
|
||||
| `setThreadBrokerContext(ctx)` | adopt a context created elsewhere as this thread's global |
|
||||
| `initThreadBrokerContext(): BrokerContext` | create + set as thread-global in one call |
|
||||
| `threadGlobalBrokerContext()` | read current thread global (lock-free) |
|
||||
|
||||
Async scoped swap (needs chronos loop): `lockGlobalBrokerContext` /
|
||||
`lockNewGlobalBrokerContext` templates.
|
||||
|
||||
---
|
||||
|
||||
## Multi-thread variants `(mt)`
|
||||
|
||||
Add `(mt)`. **Identical call surface** — `emit` stays sync `void` and `drop*`
|
||||
stay async (`Future[void]`), so the same source compiles with or without the
|
||||
tag. Cross-thread dispatch is handled under the hood. Build with `--threads:on`.
|
||||
|
||||
```nim
|
||||
EventBroker(mt):
|
||||
type Job = object
|
||||
id*: int
|
||||
|
||||
# from any thread:
|
||||
proc worker() {.thread.} =
|
||||
Job.emit(Job(id: 1)) # emit is sync void — same as single-thread
|
||||
```
|
||||
|
||||
- Same-thread calls take a direct fast path; cross-thread go through a per-bucket
|
||||
channel drained by one dispatch coroutine. fd cost is **O(threads)**, not per-broker.
|
||||
- A thread that listens must keep its event loop alive (the broker dispatches on it).
|
||||
- MT brokers accept capacity kwargs: `EventBroker(mt, queueDepth = ..., slabCapacity = ...,
|
||||
maxPayloadBytes = ..., preset = "...")`. Omit for defaults.
|
||||
|
||||
---
|
||||
|
||||
## Decision guide
|
||||
|
||||
| You want… | Use |
|
||||
|-----------|-----|
|
||||
| Notify N listeners, don't care about replies | `EventBroker` |
|
||||
| Ask one authority for an answer | `RequestBroker` |
|
||||
| Blocking call, no async context | `RequestBroker(sync)` |
|
||||
| Ask everyone, aggregate replies | `MultiRequestBroker` |
|
||||
| One-way notify a single handler, no reply | `SignalBroker` |
|
||||
| Same pattern across OS threads | add `(mt)`, `--threads:on` (call surface unchanged) |
|
||||
| Multiple isolated instances | pass a `BrokerContext` first arg |
|
||||
| Install a class method (`self.foo`) as provider/handler | `bind*` / `rebind*` sugar |
|
||||
| Register an inline body without lambda boilerplate | `listenIt` / `provideIt` / `reprovideIt` / `onSignalIt` |
|
||||
| Expose to C/C++/Python/Rust/Go | `(API)` + `registerBrokerLibrary` (see AGENTS.md) |
|
||||
|
||||
## Gotchas
|
||||
|
||||
- Handlers/providers are `raises: []` — never let an exception escape; return `err()`.
|
||||
- `setProvider` on a RequestBroker that already has one **fails** — clear first.
|
||||
- Single-thread `emit` returns immediately; await a yield before asserting in tests.
|
||||
- A non-`object`/`ref object` broker type is auto-wrapped in `distinct`; construct
|
||||
with `T(value)` and read with the base-type conversion.
|
||||
- Keep all interaction with one context on one thread (single-thread brokers are
|
||||
thread-local); cross-thread requires the `(mt)` variant.
|
||||
|
||||
---
|
||||
|
||||
## FFI API `(API)` — expose brokers as a C/C++/Python/Rust/Go shared library
|
||||
|
||||
Add `(API)` to `RequestBroker` / `EventBroker` / `SignalBroker`. Same declaration
|
||||
syntax — it additionally generates a fixed C ABI and typed foreign wrappers. Wire
|
||||
format is CBOR; wrappers carry the typed surface. Build with `-d:BrokerFfiApi
|
||||
--threads:on --app:lib`. (`SignalBroker(API)` rides `_call` one-way — enqueue
|
||||
only, no response slot; `_callAsync` rejects it with `ApiStatusOneWay`.)
|
||||
|
||||
```nim
|
||||
{.push raises: [].}
|
||||
import brokers/[event_broker, request_broker, broker_context, api_library]
|
||||
|
||||
# Plain Nim object types used in signatures are AUTO-registered — no annotation.
|
||||
type DeviceInfo* = object
|
||||
deviceId*: int64
|
||||
name*: string
|
||||
online*: bool
|
||||
|
||||
RequestBroker(API):
|
||||
type GetDevice = object # broker name == type name == response payload
|
||||
deviceId*: int64
|
||||
name*: string
|
||||
proc signature*(deviceId: int64): Future[Result[GetDevice, string]] {.async.}
|
||||
|
||||
EventBroker(API):
|
||||
type DeviceStatusChanged = object
|
||||
deviceId*: int64
|
||||
online*: bool
|
||||
timestampMs*: int64
|
||||
```
|
||||
|
||||
Providers + event emission live in one proc named **`setupProviders`** (the
|
||||
generated runtime calls it on the processing thread during `createContext`):
|
||||
|
||||
```nim
|
||||
proc setupProviders(ctx: BrokerContext): Result[void, string] =
|
||||
let r = GetDevice.setProvider(ctx, # always pass the ctx the runtime gives you
|
||||
proc(deviceId: int64): Future[Result[GetDevice, string]] {.closure, async.} =
|
||||
DeviceStatusChanged.emit(ctx, # emit is sync void (all lanes)
|
||||
DeviceStatusChanged(deviceId: deviceId, online: true, timestampMs: 0))
|
||||
ok(GetDevice(deviceId: deviceId, name: "u")))
|
||||
if r.isErr(): return err("register GetDevice: " & r.error())
|
||||
ok()
|
||||
|
||||
# MUST be the last declaration in the module:
|
||||
registerBrokerLibrary:
|
||||
name: "mylib" # MUST match --nimMainPrefix and the .so basename
|
||||
version: "1.0.0" # baked into <lib>_version() static string
|
||||
initializeRequest: InitializeRequest # post-create config broker (optional)
|
||||
shutdownRequest: ShutdownRequest # orderly teardown broker (optional)
|
||||
{.pop.}
|
||||
```
|
||||
|
||||
Build (name / `--nimMainPrefix` / `registerBrokerLibrary name` must all match):
|
||||
```
|
||||
nim c -d:BrokerFfiApi --threads:on --app:lib --path:. \
|
||||
--outdir:build --nimMainPrefix:mylib mylib.nim
|
||||
```
|
||||
|
||||
What you get — a fixed **12-function C ABI** per library: `_version`,
|
||||
`_initialize` (once per process), `_createContext` (per instance), `_shutdown(ctx)`,
|
||||
`_allocBuffer`, `_freeBuffer`, `_call` (sync round-trip), `_callAsync`
|
||||
(non-blocking, callback-completed), `_subscribe`, `_unsubscribe`, `_listApis`,
|
||||
`_getSchema`. `<lib>.h` (C) and `<lib>.hpp` (C++) are always emitted. Wire format
|
||||
is CBOR (the historical native C-ABI codegen was retired in 3.0.0).
|
||||
|
||||
| Flag | Emits | Notes |
|
||||
|------|-------|-------|
|
||||
| *(default)* | `<lib>.h`, `<lib>.hpp` | C + C++ always |
|
||||
| `-d:BrokerFfiApiGenPy` | `<lib>.py` (cbor2) | next to the `.so` |
|
||||
| `-d:BrokerFfiApiGenRust` | `<lib>_rs/` Cargo crate | ciborium + serde |
|
||||
| `-d:BrokerFfiApiGenGo` | `<lib>_go/` Go module | fxamacker/cbor |
|
||||
|
||||
FFI rules:
|
||||
- `registerBrokerLibrary` is a **no-op without `-d:BrokerFfiApi`** — no `when defined`
|
||||
guard needed; the normal in-process broker API still works.
|
||||
- `(API)` brokers ride the MT lane, so they accept the same capacity kwargs as
|
||||
`(mt)`: `RequestBroker(API, queueDepth = .., slabCapacity = .., maxPayloadBytes = ..,
|
||||
preset = "..")`.
|
||||
- `_createContext()` is readiness-synchronous: returns only after providers +
|
||||
listeners are installed and the event courier is live.
|
||||
- Inspect generated Nim with `-d:brokerDebug` → `build/broker_debug/*.gen.nim`.
|
||||
|
||||
---
|
||||
|
||||
## BrokerInterface / BrokerImplement — hierarchical / OOP layer
|
||||
|
||||
An object-oriented facade over the brokers: an **interface** groups several
|
||||
brokers behind one abstract type; an **implementation** provides per-instance
|
||||
methods. Each instance gets its own `BrokerContext`, so two instances of the same
|
||||
impl are fully isolated. Direct `instance.method()` calls **tunnel through broker
|
||||
dispatch** (so provider mocks are honored — not a plain vtable call).
|
||||
|
||||
```nim
|
||||
import brokers/broker_interface
|
||||
import brokers/broker_implement
|
||||
|
||||
BrokerInterface(IGreeter):
|
||||
EventBroker:
|
||||
type Greeted = object
|
||||
who: string
|
||||
RequestBroker:
|
||||
proc greet(name: string): Future[Result[string, string]] {.async.}
|
||||
RequestBroker:
|
||||
proc version(): Future[Result[string, string]] {.async.}
|
||||
|
||||
type GreeterImpl = ref object of IGreeter # MUST be `ref object of <Interface>`
|
||||
prefix: string
|
||||
|
||||
BrokerImplement GreeterImpl of IGreeter:
|
||||
proc new(T: typedesc[GreeterImpl], prefix: string): GreeterImpl =
|
||||
GreeterImpl(prefix: prefix) # optional ctor; create() calls it
|
||||
method greet(self: GreeterImpl, name: string): Future[Result[string, string]] {.async.} =
|
||||
ok(self.prefix & name)
|
||||
method version(self: GreeterImpl): Future[Result[string, string]] {.async.} =
|
||||
ok("v2")
|
||||
```
|
||||
|
||||
Use it:
|
||||
```nim
|
||||
let g = GreeterImpl.create(prefix = "hi ") # new() + wires providers under g.brokerCtx
|
||||
echo (waitFor g.greet("sue")).value # "hi sue" — tunnels through Greet broker
|
||||
|
||||
let base: IGreeter = g # virtual dispatch via the interface type
|
||||
echo (waitFor base.greet("x")).value # resolves to the override
|
||||
|
||||
# Each instance is isolated by its own context:
|
||||
let a = GreeterImpl.create(prefix = "a:")
|
||||
let b = GreeterImpl.create(prefix = "b:")
|
||||
# a.brokerCtx != b.brokerCtx
|
||||
|
||||
g.close() # clears THIS instance's providers + listeners; idempotent
|
||||
```
|
||||
|
||||
Event facade (instance-scoped listen/emit — context is injected for you):
|
||||
```nim
|
||||
discard g.listen(Greeted,
|
||||
proc(ev: Greeted): Future[void] {.async: (raises: []), gcsafe.} = …)
|
||||
g.emit(Greeted, Greeted(who: "bob"))
|
||||
```
|
||||
|
||||
Factory / dependency injection (resolve an impl behind the interface):
|
||||
```nim
|
||||
IGreeter.provideFactory(
|
||||
proc(cfg: string): Result[IGreeter, string] =
|
||||
ok(GreeterImpl.create(prefix = cfg)))
|
||||
let d = IGreeter.create("cfg:") # Result[IGreeter, string]; last factory wins
|
||||
```
|
||||
|
||||
Key points:
|
||||
- The broker for `proc greet` is named **`Greet`** (Capitalized verb). Address it
|
||||
directly with the instance context: `Greet.request(g.brokerCtx, "bob")`,
|
||||
`Greet.clearProvider(g.brokerCtx)` (e.g. to install a mock).
|
||||
- `Impl.create(args…)` = fresh context + `new` + provider wiring.
|
||||
`Impl.createUnderContext(ctx, args…)` wires under an externally-supplied context
|
||||
(the path the FFI runtime drives).
|
||||
- `BrokerInterface(API, IName)` lowers the sub-brokers onto the MT/FFI lane so the
|
||||
whole interface can be exposed as a shared library; `BrokerImplement` is unchanged.
|
||||
- Sub-instances returned from a method (factory pattern) share the parent's
|
||||
`classCtx` (routing) but get a distinct `instanceCtx` — see `classCtx()` /
|
||||
`instanceCtx()` accessors.
|
||||
17
.github/workflows/container-image.yml
vendored
17
.github/workflows/container-image.yml
vendored
@ -86,20 +86,34 @@ jobs:
|
||||
- name: Build binaries
|
||||
id: build
|
||||
if: ${{ steps.secrets.outcome == 'success' }}
|
||||
# Sequential make invocations: wakunode2 and logosdeliverynode each run
|
||||
# via `nimble <task>`. Under a single `make -j` the two nimble
|
||||
# invocations re-resolve git deps concurrently and clobber each other
|
||||
# in the shared ~/.nimble/pkgcache (e.g. "destination already exists",
|
||||
# "fetch-pack: invalid index-pack output"), failing the build.
|
||||
# Serializing the targets removes the race; Nim's own --parallelBuild
|
||||
# still parallelizes compilation. Mirrors the -j1 fix in ci.yml.
|
||||
run: |
|
||||
make -j${NPROC} V=1 POSTGRES=1 NIMFLAGS="-d:disableMarchNative -d:chronicles_colors:none" wakunode2
|
||||
make -j${NPROC} V=1 POSTGRES=1 NIMFLAGS="-d:disableMarchNative -d:chronicles_colors:none" logosdeliverynode
|
||||
|
||||
SHORT_REF=$(git rev-parse --short HEAD)
|
||||
|
||||
TAG=$([ "${PR_NUMBER}" == "" ] && echo "${SHORT_REF}" || echo "${PR_NUMBER}")
|
||||
IMAGE=quay.io/wakuorg/nwaku-pr:${TAG}
|
||||
LD_IMAGE=quay.io/wakuorg/nwaku-pr:${TAG}-logosdeliverynode
|
||||
|
||||
echo "image=${IMAGE}" >> $GITHUB_OUTPUT
|
||||
echo "ld_image=${LD_IMAGE}" >> $GITHUB_OUTPUT
|
||||
echo "commit_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
docker login -u ${QUAY_USER} -p ${QUAY_PASSWORD} quay.io
|
||||
docker build -t ${IMAGE} -f docker/binaries/Dockerfile.bn.amd64 --label quay.expires-after=30d .
|
||||
docker push ${IMAGE}
|
||||
|
||||
# logosdeliverynode image (same generic Dockerfile, selected via MAKE_TARGET)
|
||||
docker build -t ${LD_IMAGE} --build-arg MAKE_TARGET=logosdeliverynode -f docker/binaries/Dockerfile.bn.amd64 --label quay.expires-after=30d .
|
||||
docker push ${LD_IMAGE}
|
||||
env:
|
||||
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
|
||||
QUAY_USER: ${{ secrets.QUAY_USER }}
|
||||
@ -110,10 +124,11 @@ jobs:
|
||||
if: ${{ github.event_name == 'pull_request' && steps.secrets.outcome == 'success' }}
|
||||
with:
|
||||
message: |
|
||||
You can find the image built from this PR at
|
||||
You can find the images built from this PR at
|
||||
|
||||
```
|
||||
${{steps.build.outputs.image}}
|
||||
${{steps.build.outputs.ld_image}}
|
||||
```
|
||||
|
||||
Built from ${{ steps.build.outputs.commit_hash }}
|
||||
|
||||
15
Makefile
15
Makefile
@ -54,7 +54,7 @@ endif
|
||||
.PHONY: all test clean examples deps nimble install-nim install-nimble
|
||||
|
||||
# default target
|
||||
all: | wakunode2 liblogosdelivery
|
||||
all: | wakunode2 logosdeliverynode liblogosdelivery
|
||||
|
||||
examples: | example2 chat2 chat2bridge
|
||||
|
||||
@ -219,7 +219,7 @@ testcommon: | build-deps build
|
||||
##########
|
||||
## Waku ##
|
||||
##########
|
||||
.PHONY: testwaku wakunode2 testwakunode2 example2 chat2 chat2bridge liteprotocoltester
|
||||
.PHONY: testwaku wakunode2 logosdeliverynode testwakunode2 example2 chat2 chat2bridge liteprotocoltester
|
||||
|
||||
testwaku: | build-deps build rln-deps librln
|
||||
echo -e $(BUILD_MSG) "build/$@" && \
|
||||
@ -236,6 +236,17 @@ else
|
||||
$(NIMBLE) wakunode2
|
||||
endif
|
||||
|
||||
# Windows: build with nim directly — `nimble <task>` re-clones git deps every
|
||||
# build and they intermittently hang on the MSYS2 runner. Flags mirror logos_delivery.nimble.
|
||||
logosdeliverynode: | build-deps build deps librln
|
||||
ifeq ($(detected_OS),Windows)
|
||||
echo -e $(BUILD_MSG) "build/$@" && \
|
||||
nim c --out:build/logosdeliverynode --mm:refc --cpu:amd64 $(NIM_PARAMS) -d:chronicles_log_level=TRACE apps/logos_delivery_node/logosdeliverynode.nim
|
||||
else
|
||||
echo -e $(BUILD_MSG) "build/$@" && \
|
||||
$(NIMBLE) logosdeliverynode
|
||||
endif
|
||||
|
||||
benchmarks: | build-deps build deps librln
|
||||
echo -e $(BUILD_MSG) "build/$@" && \
|
||||
$(NIMBLE) benchmarks
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import
|
||||
std/[strutils, times, sequtils, osproc], math, results, options, testutils/unittests
|
||||
import std/[strutils, times, sequtils, osproc], math, results, testutils/unittests
|
||||
|
||||
import
|
||||
logos_delivery/waku/[
|
||||
|
||||
@ -6,7 +6,7 @@ when not (compileOption("threads")):
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[strformat, strutils, times, options, random, sequtils]
|
||||
import std/[strformat, strutils, times, random, sequtils]
|
||||
import
|
||||
confutils,
|
||||
chronicles,
|
||||
@ -48,7 +48,7 @@ import
|
||||
./config_chat2
|
||||
|
||||
import libp2p/protocols/pubsub/rpc/messages, libp2p/protocols/pubsub/pubsub
|
||||
import ../../logos_delivery/waku/rln
|
||||
import logos_delivery/waku/rln
|
||||
|
||||
const Help = """
|
||||
Commands: /[?|help|connect|nick|exit]
|
||||
@ -215,10 +215,10 @@ proc publish(c: Chat, line: string) =
|
||||
try:
|
||||
if not c.node.wakuLegacyLightPush.isNil():
|
||||
# Attempt lightpush
|
||||
(waitFor c.node.legacyLightpushPublish(some(DefaultPubsubTopic), message)).isOkOr:
|
||||
(waitFor c.node.legacyLightpushPublish(Opt.some(DefaultPubsubTopic), message)).isOkOr:
|
||||
error "failed to publish lightpush message", error = error
|
||||
else:
|
||||
(waitFor c.node.publish(some(DefaultPubsubTopic), message)).isOkOr:
|
||||
(waitFor c.node.publish(Opt.some(DefaultPubsubTopic), message)).isOkOr:
|
||||
error "failed to publish message", error = error
|
||||
except CatchableError:
|
||||
error "caught error publishing message: ", error = getCurrentExceptionMsg()
|
||||
@ -383,25 +383,25 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
echo "Connecting to static peers..."
|
||||
await connectToNodes(chat, conf.staticnodes)
|
||||
|
||||
var dnsDiscoveryUrl = none(string)
|
||||
var dnsDiscoveryUrl = Opt.none(string)
|
||||
|
||||
if conf.fleet != Fleet.none:
|
||||
# Use DNS discovery to connect to selected fleet
|
||||
echo "Connecting to " & $conf.fleet & " fleet using DNS discovery..."
|
||||
|
||||
if conf.fleet == Fleet.test:
|
||||
dnsDiscoveryUrl = some(
|
||||
dnsDiscoveryUrl = Opt.some(
|
||||
"enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im"
|
||||
)
|
||||
else:
|
||||
# Connect to sandbox by default
|
||||
dnsDiscoveryUrl = some(
|
||||
dnsDiscoveryUrl = Opt.some(
|
||||
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
|
||||
)
|
||||
elif conf.dnsDiscoveryUrl != "":
|
||||
# No pre-selected fleet. Discover nodes via DNS using user config
|
||||
info "Discovering nodes using Waku DNS discovery", url = conf.dnsDiscoveryUrl
|
||||
dnsDiscoveryUrl = some(conf.dnsDiscoveryUrl)
|
||||
dnsDiscoveryUrl = Opt.some(conf.dnsDiscoveryUrl)
|
||||
|
||||
var discoveredNodes: seq[RemotePeerInfo]
|
||||
|
||||
@ -437,17 +437,17 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
if (conf.storenode != "") or (conf.store == true):
|
||||
await node.mountStore()
|
||||
|
||||
var storenode: Option[RemotePeerInfo]
|
||||
var storenode: Opt[RemotePeerInfo]
|
||||
|
||||
if conf.storenode != "":
|
||||
let peerInfo = parsePeerInfo(conf.storenode)
|
||||
if peerInfo.isOk():
|
||||
storenode = some(peerInfo.value)
|
||||
storenode = Opt.some(peerInfo.value)
|
||||
else:
|
||||
error "Incorrect conf.storenode", error = peerInfo.error
|
||||
elif discoveredNodes.len > 0:
|
||||
echo "Store enabled, but no store nodes configured. Choosing one at random from discovered peers"
|
||||
storenode = some(discoveredNodes[rand(0 .. len(discoveredNodes) - 1)])
|
||||
storenode = Opt.some(discoveredNodes[rand(0 .. len(discoveredNodes) - 1)])
|
||||
|
||||
if storenode.isSome():
|
||||
# We have a viable storenode. Let's query it for historical messages.
|
||||
@ -537,14 +537,14 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
credIndex: conf.rlnRelayCredIndex,
|
||||
chainId: UInt256.fromBytesBE(conf.rlnRelayChainId.toBytesBE()),
|
||||
ethClientUrls: conf.ethClientUrls.mapIt(string(it)),
|
||||
creds: some(
|
||||
creds: Opt.some(
|
||||
RlnCreds(path: conf.rlnRelayCredPath, password: conf.rlnRelayCredPassword)
|
||||
),
|
||||
userMessageLimit: conf.rlnRelayUserMessageLimit,
|
||||
epochSizeSec: conf.rlnEpochSizeSec,
|
||||
)
|
||||
|
||||
waitFor node.setRlnValidator(rlnConf, spamHandler = some(spamHandler))
|
||||
waitFor node.setRlnValidator(rlnConf, spamHandler = Opt.some(spamHandler))
|
||||
|
||||
let membershipIndex = node.rln.groupManager.membershipIndex.get()
|
||||
let identityCredential = node.rln.groupManager.idCredentials.get()
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import
|
||||
results,
|
||||
chronicles,
|
||||
chronos,
|
||||
confutils,
|
||||
@ -26,7 +27,7 @@ type
|
||||
.}: LogLevel
|
||||
|
||||
nodekey* {.desc: "P2P node private key as 64 char hex string.", name: "nodekey".}:
|
||||
Option[crypto.PrivateKey]
|
||||
Opt[crypto.PrivateKey]
|
||||
|
||||
listenAddress* {.
|
||||
defaultValue: defaultListenAddress(config),
|
||||
@ -234,7 +235,7 @@ type
|
||||
|
||||
rlnRelayCredIndex* {.
|
||||
desc: "the index of the onchain commitment to use", name: "rln-relay-cred-index"
|
||||
.}: Option[uint]
|
||||
.}: Opt[uint]
|
||||
|
||||
rlnRelayDynamic* {.
|
||||
desc: "Enable waku-rln-relay with on-chain dynamic group management: true|false",
|
||||
@ -317,9 +318,9 @@ proc parseCmdArg*(T: type Port, p: string): T =
|
||||
proc completeCmdArg*(T: type Port, val: string): seq[string] =
|
||||
return @[]
|
||||
|
||||
proc parseCmdArg*(T: type Option[uint], p: string): T =
|
||||
proc parseCmdArg*(T: type Opt[uint], p: string): T =
|
||||
try:
|
||||
some(parseUint(p))
|
||||
Opt.some(parseUint(p))
|
||||
except CatchableError:
|
||||
raise newException(ValueError, "Invalid unsigned integer")
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[tables, times, strutils, hashes, sequtils, json, options],
|
||||
results,
|
||||
std/[tables, times, strutils, hashes, sequtils, json],
|
||||
chronos,
|
||||
confutils,
|
||||
chronicles,
|
||||
@ -102,7 +103,7 @@ proc toChat2(cmb: Chat2MatterBridge, jsonNode: JsonNode) {.async.} =
|
||||
|
||||
chat2_mb_transfers.inc(labelValues = ["mb_to_chat2"])
|
||||
|
||||
(await cmb.nodev2.publish(some(DefaultPubsubTopic), msg)).isOkOr:
|
||||
(await cmb.nodev2.publish(Opt.some(DefaultPubsubTopic), msg)).isOkOr:
|
||||
error "failed to publish message", error = error
|
||||
|
||||
proc toMatterbridge(
|
||||
@ -156,8 +157,8 @@ proc new*(
|
||||
nodev2Key: crypto.PrivateKey,
|
||||
nodev2BindIp: IpAddress,
|
||||
nodev2BindPort: Port,
|
||||
nodev2ExtIp = none[IpAddress](),
|
||||
nodev2ExtPort = none[Port](),
|
||||
nodev2ExtIp = Opt.none(IpAddress),
|
||||
nodev2ExtPort = Opt.none(Port),
|
||||
contentTopic: string,
|
||||
): T {.
|
||||
raises: [Defect, ValueError, KeyError, TLSStreamProtocolError, IOError, LPError]
|
||||
@ -264,7 +265,7 @@ when isMainModule:
|
||||
## config, the external port is the same as the bind port.
|
||||
let extPort =
|
||||
if nodev2ExtIp.isSome() and nodev2ExtPort.isNone():
|
||||
some(Port(uint16(conf.libp2pTcpPort) + conf.portsShift))
|
||||
Opt.some(Port(uint16(conf.libp2pTcpPort) + conf.portsShift))
|
||||
else:
|
||||
nodev2ExtPort
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import
|
||||
std/options,
|
||||
results,
|
||||
confutils,
|
||||
confutils/defs,
|
||||
confutils/std/net,
|
||||
@ -64,7 +64,7 @@ type Chat2MatterbridgeConf* = object
|
||||
|
||||
nodekey* {.
|
||||
desc: "P2P node private key as hex", defaultValueDesc: "random", name: "nodekey"
|
||||
.}: Option[crypto.PrivateKey]
|
||||
.}: Opt[crypto.PrivateKey]
|
||||
|
||||
store* {.
|
||||
desc: "Flag whether to start store protocol", defaultValue: true, name: "store"
|
||||
|
||||
@ -6,7 +6,7 @@ when not (compileOption("threads")):
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[strformat, strutils, times, options, random, sequtils]
|
||||
import std/[strformat, strutils, times, random, sequtils, sets]
|
||||
import
|
||||
confutils,
|
||||
chronicles,
|
||||
@ -31,9 +31,8 @@ import
|
||||
protocols/kademlia/types,
|
||||
protocols/service_discovery/types as sd_types,
|
||||
nameresolving/dnsresolver,
|
||||
protocols/mix/curve25519,
|
||||
protocols/mix/mix_protocol,
|
||||
] # define DNS resolution
|
||||
import libp2p_mix/[curve25519, mix_protocol]
|
||||
import
|
||||
logos_delivery/waku/[
|
||||
waku_core,
|
||||
@ -216,9 +215,9 @@ proc publish(c: Chat, line: string) {.async.} =
|
||||
|
||||
(
|
||||
waitFor c.node.lightpushPublish(
|
||||
some(c.conf.getPubsubTopic(c.node, c.contentTopic)),
|
||||
Opt.some(c.conf.getPubsubTopic(c.node, c.contentTopic)),
|
||||
message,
|
||||
none(RemotePeerInfo),
|
||||
Opt.none(RemotePeerInfo),
|
||||
true,
|
||||
)
|
||||
).isOkOr:
|
||||
@ -312,7 +311,7 @@ proc readInput(wfd: AsyncFD) {.thread, raises: [Defect, CatchableError].} =
|
||||
var alreadyUsedServicePeers {.threadvar.}: seq[RemotePeerInfo]
|
||||
|
||||
proc selectRandomServicePeer*(
|
||||
pm: PeerManager, actualPeer: Option[RemotePeerInfo], codec: string
|
||||
pm: PeerManager, actualPeer: Opt[RemotePeerInfo], codec: string
|
||||
): Result[RemotePeerInfo, void] =
|
||||
if actualPeer.isSome():
|
||||
alreadyUsedServicePeers.add(actualPeer.get())
|
||||
@ -355,7 +354,7 @@ proc maintainSubscription(
|
||||
|
||||
let subscribeErr = (
|
||||
await wakuNode.filterSubscribe(
|
||||
some(filterPubsubTopic), filterContentTopic, actualFilterPeer
|
||||
Opt.some(filterPubsubTopic), filterContentTopic, actualFilterPeer
|
||||
)
|
||||
).errorOr:
|
||||
await sleepAsync(SubscriptionMaintenance)
|
||||
@ -377,7 +376,7 @@ proc maintainSubscription(
|
||||
elif not preventPeerSwitch:
|
||||
# try again with new peer without delay
|
||||
let actualFilterPeer = selectRandomServicePeer(
|
||||
wakuNode.peerManager, some(actualFilterPeer), WakuFilterSubscribeCodec
|
||||
wakuNode.peerManager, Opt.some(actualFilterPeer), WakuFilterSubscribeCodec
|
||||
).valueOr:
|
||||
error "Failed to find new service peer. Exiting."
|
||||
noFailedServiceNodeSwitches += 1
|
||||
@ -463,10 +462,9 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
if conf.kadBootstrapNodes.len > 0:
|
||||
var kadBootstrapPeers: seq[(PeerId, seq[MultiAddress])]
|
||||
for nodeStr in conf.kadBootstrapNodes:
|
||||
let (peerId, ma) = block:
|
||||
parseFullAddress(nodeStr).isOkOr:
|
||||
error "Failed to parse kademlia bootstrap node", node = nodeStr, error
|
||||
continue
|
||||
let (peerId, ma) = parseFullAddress(nodeStr).valueOr:
|
||||
error "Failed to parse kademlia bootstrap node", node = nodeStr, error = error
|
||||
continue
|
||||
|
||||
kadBootstrapPeers.add((peerId, @[ma]))
|
||||
|
||||
@ -474,7 +472,7 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
node.mountKademlia(
|
||||
KademliaDiscoveryConf(
|
||||
bootstrapNodes: kadBootstrapPeers,
|
||||
servicesToDiscover: @[MixProtocolID],
|
||||
servicesToDiscover: toHashSet([MixProtocolID]),
|
||||
randomLookupInterval: chronos.seconds(60),
|
||||
serviceLookupInterval: chronos.seconds(60),
|
||||
kadDhtConfig: KadDHTConfig.new(),
|
||||
@ -511,25 +509,25 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
conf: conf,
|
||||
)
|
||||
|
||||
var dnsDiscoveryUrl = none(string)
|
||||
var dnsDiscoveryUrl = Opt.none(string)
|
||||
|
||||
if conf.fleet != Fleet.none:
|
||||
# Use DNS discovery to connect to selected fleet
|
||||
echo "Connecting to " & $conf.fleet & " fleet using DNS discovery..."
|
||||
|
||||
if conf.fleet == Fleet.test:
|
||||
dnsDiscoveryUrl = some(
|
||||
dnsDiscoveryUrl = Opt.some(
|
||||
"enrtree://AOGYWMBYOUIMOENHXCHILPKY3ZRFEULMFI4DOM442QSZ73TT2A7VI@test.waku.nodes.status.im"
|
||||
)
|
||||
else:
|
||||
# Connect to sandbox by default
|
||||
dnsDiscoveryUrl = some(
|
||||
dnsDiscoveryUrl = Opt.some(
|
||||
"enrtree://AIRVQ5DDA4FFWLRBCHJWUWOO6X6S4ZTZ5B667LQ6AJU6PEYDLRD5O@sandbox.waku.nodes.status.im"
|
||||
)
|
||||
elif conf.dnsDiscoveryUrl != "":
|
||||
# No pre-selected fleet. Discover nodes via DNS using user config
|
||||
info "Discovering nodes using Waku DNS discovery", url = conf.dnsDiscoveryUrl
|
||||
dnsDiscoveryUrl = some(conf.dnsDiscoveryUrl)
|
||||
dnsDiscoveryUrl = Opt.some(conf.dnsDiscoveryUrl)
|
||||
|
||||
var discoveredNodes: seq[RemotePeerInfo]
|
||||
|
||||
@ -565,17 +563,17 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
if (conf.storenode != "") or (conf.store == true):
|
||||
await node.mountStore()
|
||||
|
||||
var storenode: Option[RemotePeerInfo]
|
||||
var storenode: Opt[RemotePeerInfo]
|
||||
|
||||
if conf.storenode != "":
|
||||
let peerInfo = parsePeerInfo(conf.storenode)
|
||||
if peerInfo.isOk():
|
||||
storenode = some(peerInfo.value)
|
||||
storenode = Opt.some(peerInfo.value)
|
||||
else:
|
||||
error "Incorrect conf.storenode", error = peerInfo.error
|
||||
elif discoveredNodes.len > 0:
|
||||
echo "Store enabled, but no store nodes configured. Choosing one at random from discovered peers"
|
||||
storenode = some(discoveredNodes[rand(0 .. len(discoveredNodes) - 1)])
|
||||
storenode = Opt.some(discoveredNodes[rand(0 .. len(discoveredNodes) - 1)])
|
||||
|
||||
if storenode.isSome():
|
||||
# We have a viable storenode. Let's query it for historical messages.
|
||||
@ -620,7 +618,7 @@ proc processInput(rfd: AsyncFD, rng: crypto.Rng) {.async.} =
|
||||
if servicePeerInfo == nil or $servicePeerInfo.peerId == "":
|
||||
# Assuming that service node supports all services
|
||||
servicePeerInfo = selectRandomServicePeer(
|
||||
node.peerManager, none(RemotePeerInfo), WakuLightpushCodec
|
||||
node.peerManager, Opt.none(RemotePeerInfo), WakuLightpushCodec
|
||||
).valueOr:
|
||||
error "Couldn't find any service peer"
|
||||
quit(QuitFailure)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, chronos, std/strutils, regex
|
||||
import results, chronicles, chronos, std/strutils, regex
|
||||
|
||||
import
|
||||
eth/keys,
|
||||
@ -32,7 +32,7 @@ type
|
||||
.}: LogLevel
|
||||
|
||||
nodekey* {.desc: "P2P node private key as 64 char hex string.", name: "nodekey".}:
|
||||
Option[crypto.PrivateKey]
|
||||
Opt[crypto.PrivateKey]
|
||||
|
||||
listenAddress* {.
|
||||
defaultValue: defaultListenAddress(config),
|
||||
@ -287,9 +287,9 @@ proc parseCmdArg*(T: type Port, p: string): T =
|
||||
proc completeCmdArg*(T: type Port, val: string): seq[string] =
|
||||
return @[]
|
||||
|
||||
proc parseCmdArg*(T: type Option[uint], p: string): T =
|
||||
proc parseCmdArg*(T: type Opt[uint], p: string): T =
|
||||
try:
|
||||
some(parseUint(p))
|
||||
Opt.some(parseUint(p))
|
||||
except CatchableError:
|
||||
raise newException(ValueError, "Invalid unsigned integer")
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ else:
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, net, strformat],
|
||||
std/[net, strformat],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronos, results, options
|
||||
import chronos, results
|
||||
import logos_delivery/waku/[waku_node, waku_core]
|
||||
import publisher_base
|
||||
|
||||
@ -18,7 +18,7 @@ method send*(
|
||||
): Future[Result[void, string]] {.async.} =
|
||||
# when error it must return original error desc due the text is used for distinction between error types in metrics.
|
||||
discard (
|
||||
await self.wakuNode.legacyLightpushPublish(some(topic), message, servicePeer)
|
||||
await self.wakuNode.legacyLightpushPublish(Opt.some(topic), message, servicePeer)
|
||||
).valueOr:
|
||||
return err(error)
|
||||
return ok()
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, strutils, os, sequtils, net],
|
||||
results,
|
||||
std/[strutils, os, sequtils, net],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
@ -95,7 +96,7 @@ when isMainModule:
|
||||
|
||||
wakuNodeConf.shards = @[conf.shard]
|
||||
wakuNodeConf.contentTopics = conf.contentTopics
|
||||
wakuNodeConf.clusterId = some(conf.clusterId)
|
||||
wakuNodeConf.clusterId = Opt.some(conf.clusterId)
|
||||
## TODO: Depending on the tester needs we might extend here with shards, clusterId, etc...
|
||||
|
||||
wakuNodeConf.metricsServer = true
|
||||
@ -190,7 +191,7 @@ when isMainModule:
|
||||
quit(QuitFailure)
|
||||
|
||||
serviceNodePeerInfo = selectRandomServicePeer(
|
||||
waku.node.peerManager, none(RemotePeerInfo), codec
|
||||
waku.node.peerManager, Opt.none(RemotePeerInfo), codec
|
||||
).valueOr:
|
||||
error "Service node selection failed"
|
||||
quit(QuitFailure)
|
||||
|
||||
@ -188,7 +188,7 @@ proc publishMessages(
|
||||
if not preventPeerSwitch and noFailedPush > maxFailedPush:
|
||||
info "Max push failure limit reached, Try switching peer."
|
||||
actualServicePeer = selectRandomServicePeer(
|
||||
wakuNode.peerManager, some(actualServicePeer), WakuLightPushCodec
|
||||
wakuNode.peerManager, Opt.some(actualServicePeer), WakuLightPushCodec
|
||||
).valueOr:
|
||||
error "Failed to find new service peer. Exiting."
|
||||
noFailedServiceNodeSwitches += 1
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
## subscribe to messages without relay
|
||||
|
||||
import
|
||||
std/options,
|
||||
system/ansi_c,
|
||||
chronicles,
|
||||
chronos,
|
||||
@ -74,7 +73,7 @@ proc maintainSubscription(
|
||||
|
||||
let subscribeErr = (
|
||||
await wakuNode.filterSubscribe(
|
||||
some(filterPubsubTopic), filterContentTopic, actualFilterPeer
|
||||
Opt.some(filterPubsubTopic), filterContentTopic, actualFilterPeer
|
||||
)
|
||||
).errorOr:
|
||||
await sleepAsync(SubscriptionMaintenanceMs)
|
||||
@ -99,7 +98,7 @@ proc maintainSubscription(
|
||||
elif not preventPeerSwitch:
|
||||
# try again with new peer without delay
|
||||
actualFilterPeer = selectRandomServicePeer(
|
||||
wakuNode.peerManager, some(actualFilterPeer), WakuFilterSubscribeCodec
|
||||
wakuNode.peerManager, Opt.some(actualFilterPeer), WakuFilterSubscribeCodec
|
||||
).valueOr:
|
||||
error "Failed to find new service peer. Exiting."
|
||||
noFailedServiceNodeSwitches += 1
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, net, sysrand, random, strformat, strutils, sequtils],
|
||||
results,
|
||||
std/[net, sysrand, random, strformat, strutils, sequtils],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
@ -53,7 +54,7 @@ proc translateToRemotePeerInfo*(peerAddress: string): Result[RemotePeerInfo, voi
|
||||
## Note: This is kept for future use.
|
||||
proc selectRandomCapablePeer*(
|
||||
pm: PeerManager, codec: string, pubsubTopic: PubsubTopic
|
||||
): Future[Option[RemotePeerInfo]] {.async.} =
|
||||
): Future[Opt[RemotePeerInfo]] {.async.} =
|
||||
var cap = Capabilities.Filter
|
||||
if codec.contains("lightpush"):
|
||||
cap = Capabilities.Lightpush
|
||||
@ -65,9 +66,9 @@ proc selectRandomCapablePeer*(
|
||||
trace "Found supportive peers count", count = supportivePeers.len()
|
||||
trace "Found supportive peers", supportivePeers = $supportivePeers
|
||||
if supportivePeers.len == 0:
|
||||
return none(RemotePeerInfo)
|
||||
return Opt.none(RemotePeerInfo)
|
||||
|
||||
var found = none(RemotePeerInfo)
|
||||
var found = Opt.none(RemotePeerInfo)
|
||||
while found.isNone() and supportivePeers.len > 0:
|
||||
let rndPeerIndex = rand(0 .. supportivePeers.len - 1)
|
||||
let randomPeer = supportivePeers[rndPeerIndex]
|
||||
@ -80,7 +81,7 @@ proc selectRandomCapablePeer*(
|
||||
let connOpt = pm.dialPeer(randomPeer, codec)
|
||||
if (await connOpt.withTimeout(10.seconds)):
|
||||
if connOpt.value().isSome():
|
||||
found = some(randomPeer)
|
||||
found = Opt.some(randomPeer)
|
||||
info "Dialing successful",
|
||||
peer = constructMultiaddrStr(randomPeer), codec = codec
|
||||
else:
|
||||
@ -94,7 +95,7 @@ proc selectRandomCapablePeer*(
|
||||
# Debugging PX gathered peers connectivity
|
||||
proc tryCallAllPxPeers*(
|
||||
pm: PeerManager, codec: string, pubsubTopic: PubsubTopic
|
||||
): Future[Option[seq[RemotePeerInfo]]] {.async.} =
|
||||
): Future[Opt[seq[RemotePeerInfo]]] {.async.} =
|
||||
var capability = Capabilities.Filter
|
||||
if codec.contains("lightpush"):
|
||||
capability = Capabilities.Lightpush
|
||||
@ -107,7 +108,7 @@ proc tryCallAllPxPeers*(
|
||||
info "Found supportive peers count", count = supportivePeers.len()
|
||||
info "Found supportive peers", supportivePeers = $supportivePeers
|
||||
if supportivePeers.len == 0:
|
||||
return none(seq[RemotePeerInfo])
|
||||
return Opt.none(seq[RemotePeerInfo])
|
||||
|
||||
var okPeers: seq[RemotePeerInfo] = @[]
|
||||
|
||||
@ -152,7 +153,7 @@ proc tryCallAllPxPeers*(
|
||||
echo "PX returned peers found callable for " & codec & " / " & $capability & ":\n"
|
||||
echo okPeersStr
|
||||
|
||||
return some(okPeers)
|
||||
return Opt.some(okPeers)
|
||||
|
||||
proc pxLookupServiceNode*(
|
||||
node: WakuNode, conf: LiteProtocolTesterConf
|
||||
@ -168,7 +169,7 @@ proc pxLookupServiceNode*(
|
||||
node.peerManager.addServicePeer(peerExchangeNode, WakuPeerExchangeCodec)
|
||||
|
||||
try:
|
||||
await node.mountPeerExchange(some(conf.clusterId))
|
||||
await node.mountPeerExchange(Opt.some(conf.clusterId))
|
||||
except CatchableError:
|
||||
error "failed to mount waku peer-exchange protocol",
|
||||
error = getCurrentExceptionMsg()
|
||||
@ -207,7 +208,7 @@ var alreadyUsedServicePeers {.threadvar.}: seq[RemotePeerInfo]
|
||||
|
||||
## Select service peers by codec from peer store randomly.
|
||||
proc selectRandomServicePeer*(
|
||||
pm: PeerManager, actualPeer: Option[RemotePeerInfo], codec: string
|
||||
pm: PeerManager, actualPeer: Opt[RemotePeerInfo], codec: string
|
||||
): Result[RemotePeerInfo, void] =
|
||||
if actualPeer.isSome():
|
||||
alreadyUsedServicePeers.add(actualPeer.get())
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[sets, tables, sequtils, options, strformat],
|
||||
std/[sets, tables, sequtils, strformat],
|
||||
chronos/timer as chtimer,
|
||||
chronicles,
|
||||
chronos,
|
||||
@ -123,10 +123,10 @@ proc addMessage*(
|
||||
|
||||
lpt_receiver_sender_peer_count.set(value = self.len)
|
||||
|
||||
proc lastMessageArrivedAt*(self: Statistics): Option[Moment] =
|
||||
proc lastMessageArrivedAt*(self: Statistics): Opt[Moment] =
|
||||
if self.receivedMessages > 0:
|
||||
return some(self.helper.prevArrivedAt)
|
||||
return none(Moment)
|
||||
return Opt.some(self.helper.prevArrivedAt)
|
||||
return Opt.none(Moment)
|
||||
|
||||
proc lossCount*(self: Statistics): uint32 =
|
||||
self.helper.maxIndex - self.receivedMessages
|
||||
@ -271,7 +271,7 @@ proc jsonStats*(self: PerPeerStatistics): string =
|
||||
"{\"result:\": \"Error while generating json stats: " & getCurrentExceptionMsg() &
|
||||
"\"}"
|
||||
|
||||
proc lastMessageArrivedAt*(self: PerPeerStatistics): Option[Moment] =
|
||||
proc lastMessageArrivedAt*(self: PerPeerStatistics): Opt[Moment] =
|
||||
var lastArrivedAt = Moment.init(0, Millisecond)
|
||||
for stat in self.values:
|
||||
let lastMsgFromPeerAt = stat.lastMessageArrivedAt().valueOr:
|
||||
@ -281,9 +281,9 @@ proc lastMessageArrivedAt*(self: PerPeerStatistics): Option[Moment] =
|
||||
lastArrivedAt = lastMsgFromPeerAt
|
||||
|
||||
if lastArrivedAt == Moment.init(0, Millisecond):
|
||||
return none(Moment)
|
||||
return Opt.none(Moment)
|
||||
|
||||
return some(lastArrivedAt)
|
||||
return Opt.some(lastArrivedAt)
|
||||
|
||||
proc checkIfAllMessagesReceived*(
|
||||
self: PerPeerStatistics, maxWaitForLastMessage: Duration
|
||||
|
||||
@ -37,7 +37,7 @@ type LiteProtocolTesterConf* = object
|
||||
desc:
|
||||
"Loads configuration from a TOML file (cmd-line parameters take precedence) for the light waku node",
|
||||
name: "config-file"
|
||||
.}: Option[InputFile]
|
||||
.}: Opt[InputFile]
|
||||
|
||||
## Log configuration
|
||||
logLevel* {.
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
results,
|
||||
chronicles,
|
||||
json_serialization,
|
||||
json_serialization/std/options,
|
||||
json_serialization/pkg/results,
|
||||
json_serialization/lexer
|
||||
|
||||
import logos_delivery/waku/rest_api/endpoint/serdes
|
||||
@ -34,13 +35,13 @@ proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var ProtocolTesterMessage
|
||||
) {.gcsafe, raises: [SerializationError, IOError].} =
|
||||
var
|
||||
sender: Option[string]
|
||||
index: Option[uint32]
|
||||
count: Option[uint32]
|
||||
startedAt: Option[int64]
|
||||
sinceStart: Option[int64]
|
||||
sincePrev: Option[int64]
|
||||
size: Option[uint64]
|
||||
sender: Opt[string]
|
||||
index: Opt[uint32]
|
||||
count: Opt[uint32]
|
||||
startedAt: Opt[int64]
|
||||
sinceStart: Opt[int64]
|
||||
sincePrev: Opt[int64]
|
||||
size: Opt[uint64]
|
||||
|
||||
for fieldName in readObjectFields(reader):
|
||||
case fieldName
|
||||
@ -49,43 +50,43 @@ proc readValue*(
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `sender` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
sender = some(reader.readValue(string))
|
||||
sender = Opt.some(reader.readValue(string))
|
||||
of "index":
|
||||
if index.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `index` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
index = some(reader.readValue(uint32))
|
||||
index = Opt.some(reader.readValue(uint32))
|
||||
of "count":
|
||||
if count.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `count` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
count = some(reader.readValue(uint32))
|
||||
count = Opt.some(reader.readValue(uint32))
|
||||
of "startedAt":
|
||||
if startedAt.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `startedAt` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
startedAt = some(reader.readValue(int64))
|
||||
startedAt = Opt.some(reader.readValue(int64))
|
||||
of "sinceStart":
|
||||
if sinceStart.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `sinceStart` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
sinceStart = some(reader.readValue(int64))
|
||||
sinceStart = Opt.some(reader.readValue(int64))
|
||||
of "sincePrev":
|
||||
if sincePrev.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `sincePrev` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
sincePrev = some(reader.readValue(int64))
|
||||
sincePrev = Opt.some(reader.readValue(int64))
|
||||
of "size":
|
||||
if size.isSome():
|
||||
reader.raiseUnexpectedField(
|
||||
"Multiple `size` fields found", "ProtocolTesterMessage"
|
||||
)
|
||||
size = some(reader.readValue(uint64))
|
||||
size = Opt.some(reader.readValue(uint64))
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import results, options, chronos
|
||||
import results, chronos
|
||||
import logos_delivery/waku/[waku_node, waku_core, waku_lightpush, waku_lightpush/common]
|
||||
import publisher_base
|
||||
|
||||
@ -18,10 +18,12 @@ method send*(
|
||||
): Future[Result[void, string]] {.async.} =
|
||||
# when error it must return original error desc due the text is used for distinction between error types in metrics.
|
||||
discard (
|
||||
await self.wakuNode.lightpushPublish(some(topic), message, some(servicePeer))
|
||||
await self.wakuNode.lightpushPublish(
|
||||
Opt.some(topic), message, Opt.some(servicePeer)
|
||||
)
|
||||
).valueOr:
|
||||
if error.code == LightPushErrorCode.NO_PEERS_TO_RELAY and
|
||||
error.desc != some("No peers for topic, skipping publish"):
|
||||
error.desc != Opt.some("No peers for topic, skipping publish"):
|
||||
# TODO: We need better separation of errors happening on the client side or the server side.-
|
||||
return err("dial_failure")
|
||||
else:
|
||||
|
||||
98
apps/logos_delivery_node/logosdeliverynode.nim
Normal file
98
apps/logos_delivery_node/logosdeliverynode.nim
Normal file
@ -0,0 +1,98 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, strutils, sequtils, net],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
system/ansi_c,
|
||||
libp2p/crypto/crypto
|
||||
import
|
||||
../../tools/confutils/cli_args,
|
||||
logos_delivery/logos_delivery,
|
||||
logos_delivery/waku/common/logging
|
||||
|
||||
logScope:
|
||||
topics = "logosdeliverynode main"
|
||||
|
||||
const git_version* {.strdefine.} = "n/a"
|
||||
|
||||
{.pop.}
|
||||
# @TODO confutils.nim(775, 17) Error: can raise an unlisted exception: ref IOError
|
||||
when isMainModule:
|
||||
## Node setup happens in 6 phases:
|
||||
## 1. Set up storage
|
||||
## 2. Initialize node
|
||||
## 3. Mount and initialize configured protocols
|
||||
## 4. Start node and mounted protocols
|
||||
## 5. Start monitoring tools and external interfaces
|
||||
## 6. Setup graceful shutdown hooks
|
||||
|
||||
const versionString = "version / git commit hash: " & git_version
|
||||
|
||||
var wakuNodeConf = WakuNodeConf.load(version = versionString).valueOr:
|
||||
error "failure while loading the configuration", error = error
|
||||
quit(QuitFailure)
|
||||
|
||||
## Also called within LogosDelivery.new. The call to startRestServerEssentials
|
||||
## needs the following line
|
||||
logging.setupLog(wakuNodeConf.logLevel, wakuNodeConf.logFormat)
|
||||
|
||||
case wakuNodeConf.cmd
|
||||
of generateRlnKeystore:
|
||||
error "generateRlnKeystore not supported by logos_delivery_node; use wakunode2"
|
||||
quit(QuitFailure)
|
||||
of noCommand:
|
||||
# `LogosDelivery` derives the per-layer config from `WakuNodeConf` itself
|
||||
# (it runs `toWakuConf` internally), then builds the full stack bottom-up:
|
||||
# Waku <- MessagingClient <- ReliableChannelManager
|
||||
var node = (waitFor LogosDelivery.new(wakuNodeConf)).valueOr:
|
||||
error "LogosDelivery initialization failed", error = error
|
||||
quit(QuitFailure)
|
||||
|
||||
(waitFor node.start()).isOkOr:
|
||||
error "Starting LogosDelivery failed", error = error
|
||||
quit(QuitFailure)
|
||||
|
||||
info "Setting up shutdown hooks"
|
||||
proc asyncStopper(node: LogosDelivery) {.async: (raises: [Exception]).} =
|
||||
(await node.stop()).isOkOr:
|
||||
error "LogosDelivery shutdown failed", error = error
|
||||
quit(QuitSuccess)
|
||||
|
||||
# Handle Ctrl-C SIGINT
|
||||
proc handleCtrlC() {.noconv.} =
|
||||
when defined(windows):
|
||||
# workaround for https://github.com/nim-lang/Nim/issues/4057
|
||||
setupForeignThreadGc()
|
||||
notice "Shutting down after receiving SIGINT"
|
||||
asyncSpawn asyncStopper(node)
|
||||
|
||||
setControlCHook(handleCtrlC)
|
||||
|
||||
# Handle SIGTERM
|
||||
when defined(posix):
|
||||
proc handleSigterm(signal: cint) {.noconv.} =
|
||||
notice "Shutting down after receiving SIGTERM"
|
||||
asyncSpawn asyncStopper(node)
|
||||
|
||||
c_signal(ansi_c.SIGTERM, handleSigterm)
|
||||
|
||||
# Handle SIGSEGV
|
||||
when defined(posix):
|
||||
proc handleSigsegv(signal: cint) {.noconv.} =
|
||||
# Require --debugger:native
|
||||
fatal "Shutting down after receiving SIGSEGV"
|
||||
|
||||
# Not available in -d:release mode
|
||||
writeStackTrace()
|
||||
|
||||
(waitFor node.stop()).isOkOr:
|
||||
error "LogosDelivery shutdown failed", error = error
|
||||
quit(QuitFailure)
|
||||
|
||||
c_signal(ansi_c.SIGSEGV, handleSigsegv)
|
||||
|
||||
info "Node setup complete"
|
||||
|
||||
runForever()
|
||||
10
apps/logos_delivery_node/nim.cfg
Normal file
10
apps/logos_delivery_node/nim.cfg
Normal file
@ -0,0 +1,10 @@
|
||||
-d:chronicles_line_numbers
|
||||
-d:discv5_protocol_id="d5waku"
|
||||
-d:chronicles_runtime_filtering=on
|
||||
-d:chronicles_sinks="textlines,json"
|
||||
-d:chronicles_default_output_device=dynamic
|
||||
# Disabling the following topics from nim-eth and nim-dnsdisc since some types cannot be serialized
|
||||
-d:chronicles_disabled_topics="eth,dnsdisc.client"
|
||||
# Results in empty output for some reason
|
||||
#-d:"chronicles_enabled_topics=GossipSub:TRACE,WakuRelay:TRACE"
|
||||
path = "../.."
|
||||
@ -422,7 +422,7 @@ proc initAndStartApp(
|
||||
|
||||
let
|
||||
# some hardcoded parameters
|
||||
rng = keys.newRng()
|
||||
rng = crypto.newRng()
|
||||
key = crypto.PrivateKey.random(Secp256k1, rng)[]
|
||||
nodeTcpPort = Port(60000)
|
||||
nodeUdpPort = Port(9000)
|
||||
@ -433,7 +433,9 @@ proc initAndStartApp(
|
||||
var builder = EnrBuilder.init(key)
|
||||
|
||||
builder.withIpAddressAndPorts(
|
||||
ipAddr = some(extIp), tcpPort = some(nodeTcpPort), udpPort = some(nodeUdpPort)
|
||||
ipAddr = Opt.some(extIp),
|
||||
tcpPort = Opt.some(nodeTcpPort),
|
||||
udpPort = Opt.some(nodeUdpPort),
|
||||
)
|
||||
builder.withWakuCapabilities(flags)
|
||||
|
||||
@ -450,7 +452,7 @@ proc initAndStartApp(
|
||||
|
||||
nodeBuilder.withNodeKey(key)
|
||||
nodeBuilder.withRecord(record)
|
||||
nodeBuilder.withSwitchConfiguration(maxConnections = some(MaxConnectedPeers))
|
||||
nodeBuilder.withSwitchConfiguration(maxConnections = Opt.some(MaxConnectedPeers))
|
||||
|
||||
nodeBuilder.withPeerManagerConfig(
|
||||
maxConnections = MaxConnectedPeers,
|
||||
@ -473,7 +475,7 @@ proc initAndStartApp(
|
||||
|
||||
# discv5
|
||||
let discv5Conf = WakuDiscoveryV5Config(
|
||||
discv5Config: none(DiscoveryConfig),
|
||||
discv5Config: Opt.none(DiscoveryConfig),
|
||||
address: bindIp,
|
||||
port: nodeUdpPort,
|
||||
privateKey: keys.PrivateKey(key.skkey),
|
||||
@ -481,7 +483,7 @@ proc initAndStartApp(
|
||||
autoupdateRecord: false,
|
||||
)
|
||||
|
||||
let wakuDiscv5 = WakuDiscoveryV5.new(node.rng, discv5Conf, some(record))
|
||||
let wakuDiscv5 = WakuDiscoveryV5.new(node.rng, discv5Conf, Opt.some(record))
|
||||
|
||||
try:
|
||||
wakuDiscv5.protocol.open()
|
||||
@ -610,11 +612,11 @@ when isMainModule:
|
||||
if conf.rlnRelay and conf.rlnRelayEthContractAddress != "":
|
||||
let rlnConf = WakuRlnConfig(
|
||||
dynamic: conf.rlnRelayDynamic,
|
||||
credIndex: some(uint(0)),
|
||||
credIndex: Opt.some(uint(0)),
|
||||
ethContractAddress: conf.rlnRelayEthContractAddress,
|
||||
ethClientUrls: conf.ethClientUrls.mapIt(string(it)),
|
||||
epochSizeSec: conf.rlnEpochSizeSec,
|
||||
creds: none(RlnCreds),
|
||||
creds: Opt.none(RlnCreds),
|
||||
onFatalErrorAction: onFatalErrorAction,
|
||||
)
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import
|
||||
results,
|
||||
std/[strutils, sequtils, tables, strformat],
|
||||
confutils,
|
||||
chronos,
|
||||
@ -215,7 +216,7 @@ proc main(rng: Rng): Future[int] {.async.} =
|
||||
let netConfig = NetConfig.init(
|
||||
bindIp = bindIp,
|
||||
bindPort = nodeTcpPort,
|
||||
wsBindPort = some(wsBindPort),
|
||||
wsBindPort = Opt.some(wsBindPort),
|
||||
wsEnabled = isWs,
|
||||
wssEnabled = isWss,
|
||||
)
|
||||
@ -244,7 +245,9 @@ proc main(rng: Rng): Future[int] {.async.} =
|
||||
builder.withRecord(record)
|
||||
builder.withNetworkConfiguration(netConfig.tryGet())
|
||||
builder.withSwitchConfiguration(
|
||||
secureKey = some(keyPath), secureCert = some(certPath), nameResolver = resolver
|
||||
secureKey = Opt.some(keyPath),
|
||||
secureCert = Opt.some(certPath),
|
||||
nameResolver = resolver,
|
||||
)
|
||||
|
||||
let node = builder.build().tryGet()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, strutils, sequtils, net],
|
||||
std/[strutils, sequtils, net],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import std/options
|
||||
import chronos, results, confutils, confutils/defs
|
||||
import logos_delivery
|
||||
|
||||
@ -67,11 +66,9 @@ when isMainModule:
|
||||
if args.ethRpcEndpoint == "":
|
||||
# Create a basic configuration for the Waku node
|
||||
# No RLN as we don't have an ETH RPC Endpoint
|
||||
conf.mode = Core
|
||||
conf.preset = "logos.dev"
|
||||
else:
|
||||
# Connect to TWN, use ETH RPC Endpoint for RLN
|
||||
conf.mode = Core
|
||||
conf.preset = "twn"
|
||||
conf.ethClientUrls = @[EthRpcUrl(args.ethRpcEndpoint)]
|
||||
|
||||
|
||||
@ -43,13 +43,13 @@ proc messagePushHandler(
|
||||
contentTopic = message.contentTopic,
|
||||
timestamp = message.timestamp
|
||||
|
||||
proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
proc setupAndSubscribe(rng: crypto.Rng) {.async.} =
|
||||
# use notice to filter all waku messaging
|
||||
setupLog(logging.LogLevel.NOTICE, logging.LogFormat.TEXT)
|
||||
|
||||
notice "starting subscriber", wakuPort = wakuPort
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng)[]
|
||||
ip = parseIpAddress("0.0.0.0")
|
||||
flags = CapabilitiesBitfield.init(relay = true)
|
||||
|
||||
|
||||
@ -48,13 +48,13 @@ proc splitPeerIdAndAddr(maddr: string): (string, string) =
|
||||
peerId = parts[1]
|
||||
return (address, peerId)
|
||||
|
||||
proc setupAndPublish(rng: ref HmacDrbgContext, conf: LightPushMixConf) {.async.} =
|
||||
proc setupAndPublish(rng: crypto.Rng, 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()
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng).get()
|
||||
ip = parseIpAddress("0.0.0.0")
|
||||
flags = CapabilitiesBitfield.init(relay = true)
|
||||
|
||||
@ -84,7 +84,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext, conf: LightPushMixConf) {.async.}
|
||||
)
|
||||
node.mountLightPushClient()
|
||||
try:
|
||||
await node.mountPeerExchange(some(uint16(clusterId)))
|
||||
await node.mountPeerExchange(Opt.some(uint16(clusterId)))
|
||||
except CatchableError:
|
||||
error "failed to mount waku peer-exchange protocol",
|
||||
error = getCurrentExceptionMsg()
|
||||
@ -164,8 +164,9 @@ proc setupAndPublish(rng: ref HmacDrbgContext, conf: LightPushMixConf) {.async.}
|
||||
timestamp: getNowInNanosecondTime(),
|
||||
) # current timestamp
|
||||
|
||||
let res =
|
||||
await node.wakuLightpushClient.publish(some(LightpushPubsubTopic), message, conn)
|
||||
let res = await node.wakuLightpushClient.publish(
|
||||
Opt.some(LightpushPubsubTopic), message, conn
|
||||
)
|
||||
|
||||
let startTime = getNowInNanosecondTime()
|
||||
|
||||
|
||||
@ -35,13 +35,13 @@ const
|
||||
LightpushPubsubTopic = PubsubTopic("/waku/2/rs/1/0")
|
||||
LightpushContentTopic = ContentTopic("/examples/1/light-pubsub-example/proto")
|
||||
|
||||
proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
proc setupAndPublish(rng: crypto.Rng) {.async.} =
|
||||
# use notice to filter all waku messaging
|
||||
setupLog(logging.LogLevel.NOTICE, logging.LogFormat.TEXT)
|
||||
|
||||
notice "starting publisher", wakuPort = wakuPort
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get()
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng).get()
|
||||
ip = parseIpAddress("0.0.0.0")
|
||||
flags = CapabilitiesBitfield.init(relay = true)
|
||||
|
||||
@ -87,7 +87,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
quit(QuitFailure)
|
||||
|
||||
let res = await node.legacyLightpushPublish(
|
||||
some(LightpushPubsubTopic), message, lightpushPeer
|
||||
Opt.some(LightpushPubsubTopic), message, lightpushPeer
|
||||
)
|
||||
|
||||
if res.isOk:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import
|
||||
results,
|
||||
std/[tables, times, sequtils],
|
||||
stew/byteutils,
|
||||
chronicles,
|
||||
@ -37,13 +38,13 @@ const bootstrapNode =
|
||||
const wakuPort = 60000
|
||||
const discv5Port = 9000
|
||||
|
||||
proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
proc setupAndPublish(rng: crypto.Rng) {.async.} =
|
||||
# use notice to filter all waku messaging
|
||||
setupLog(logging.LogLevel.NOTICE, logging.LogFormat.TEXT)
|
||||
|
||||
notice "starting publisher", wakuPort = wakuPort, discv5Port = discv5Port
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[]).get()
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng).get()
|
||||
ip = parseIpAddress("0.0.0.0")
|
||||
flags = CapabilitiesBitfield.init(relay = true)
|
||||
|
||||
@ -63,7 +64,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
discard bootstrapNodeEnr.fromURI(bootstrapNode)
|
||||
|
||||
let discv5Conf = WakuDiscoveryV5Config(
|
||||
discv5Config: none(DiscoveryConfig),
|
||||
discv5Config: Opt.none(DiscoveryConfig),
|
||||
address: ip,
|
||||
port: Port(discv5Port),
|
||||
privateKey: keys.PrivateKey(nodeKey.skkey),
|
||||
@ -75,8 +76,8 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
let wakuDiscv5 = WakuDiscoveryV5.new(
|
||||
node.rng,
|
||||
discv5Conf,
|
||||
some(node.enr),
|
||||
some(node.peerManager),
|
||||
Opt.some(node.enr),
|
||||
Opt.some(node.peerManager),
|
||||
node.topicSubscriptionQueue,
|
||||
)
|
||||
|
||||
@ -119,7 +120,7 @@ proc setupAndPublish(rng: ref HmacDrbgContext) {.async.} =
|
||||
timestamp: now(),
|
||||
) # current timestamp
|
||||
|
||||
let res = await node.publish(some(pubSubTopic), message)
|
||||
let res = await node.publish(Opt.some(pubSubTopic), message)
|
||||
|
||||
if res.isOk:
|
||||
notice "published message",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import
|
||||
results,
|
||||
std/[tables, sequtils],
|
||||
stew/byteutils,
|
||||
chronicles,
|
||||
@ -35,13 +36,13 @@ const bootstrapNode =
|
||||
const wakuPort = 50000
|
||||
const discv5Port = 8000
|
||||
|
||||
proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
proc setupAndSubscribe(rng: crypto.Rng) {.async.} =
|
||||
# use notice to filter all waku messaging
|
||||
setupLog(logging.LogLevel.NOTICE, logging.LogFormat.TEXT)
|
||||
|
||||
notice "starting subscriber", wakuPort = wakuPort, discv5Port = discv5Port
|
||||
let
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||
nodeKey = crypto.PrivateKey.random(Secp256k1, rng)[]
|
||||
ip = parseIpAddress("0.0.0.0")
|
||||
flags = CapabilitiesBitfield.init(relay = true)
|
||||
|
||||
@ -61,7 +62,7 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
discard bootstrapNodeEnr.fromURI(bootstrapNode)
|
||||
|
||||
let discv5Conf = WakuDiscoveryV5Config(
|
||||
discv5Config: none(DiscoveryConfig),
|
||||
discv5Config: Opt.none(DiscoveryConfig),
|
||||
address: ip,
|
||||
port: Port(discv5Port),
|
||||
privateKey: keys.PrivateKey(nodeKey.skkey),
|
||||
@ -73,8 +74,8 @@ proc setupAndSubscribe(rng: ref HmacDrbgContext) {.async.} =
|
||||
let wakuDiscv5 = WakuDiscoveryV5.new(
|
||||
node.rng,
|
||||
discv5Conf,
|
||||
some(node.enr),
|
||||
some(node.peerManager),
|
||||
Opt.some(node.enr),
|
||||
Opt.some(node.peerManager),
|
||||
node.topicSubscriptionQueue,
|
||||
)
|
||||
|
||||
|
||||
@ -3,15 +3,16 @@
|
||||
import tools/confutils/cli_args
|
||||
import logos_delivery/waku/[common/logging, waku, factory/networks_config]
|
||||
import
|
||||
std/[options, strutils, os, sequtils],
|
||||
results,
|
||||
std/[strutils, os, sequtils],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
libp2p/crypto/crypto
|
||||
|
||||
export
|
||||
networks_config, waku, logging, options, strutils, os, sequtils, stewNet, chronicles,
|
||||
chronos, metrics, crypto
|
||||
networks_config, waku, logging, strutils, os, sequtils, stewNet, chronicles, chronos,
|
||||
metrics, crypto
|
||||
|
||||
proc setup*(): Waku =
|
||||
const versionString = "version / git commit hash: " & waku.git_version
|
||||
@ -29,18 +30,18 @@ proc setup*(): Waku =
|
||||
|
||||
# Override configuration
|
||||
conf.maxMessageSize = twnNetworkConf.maxMessageSize
|
||||
conf.clusterId = some(twnNetworkConf.clusterId)
|
||||
conf.clusterId = Opt.some(twnNetworkConf.clusterId)
|
||||
conf.rlnRelayEthContractAddress = twnNetworkConf.rlnRelayEthContractAddress
|
||||
conf.rlnRelayDynamic = some(twnNetworkConf.rlnRelayDynamic)
|
||||
conf.discv5Discovery = some(twnNetworkConf.discv5Discovery)
|
||||
conf.rlnRelayDynamic = Opt.some(twnNetworkConf.rlnRelayDynamic)
|
||||
conf.discv5Discovery = Opt.some(twnNetworkConf.discv5Discovery)
|
||||
conf.discv5BootstrapNodes =
|
||||
conf.discv5BootstrapNodes & twnNetworkConf.discv5BootstrapNodes
|
||||
conf.rlnEpochSizeSec = some(twnNetworkConf.rlnEpochSizeSec)
|
||||
conf.rlnRelayUserMessageLimit = some(twnNetworkConf.rlnRelayUserMessageLimit)
|
||||
conf.rlnEpochSizeSec = Opt.some(twnNetworkConf.rlnEpochSizeSec)
|
||||
conf.rlnRelayUserMessageLimit = Opt.some(twnNetworkConf.rlnRelayUserMessageLimit)
|
||||
|
||||
# Only set rlnRelay to true if relay is configured
|
||||
if conf.relay:
|
||||
conf.rlnRelay = some(twnNetworkConf.rlnRelay)
|
||||
conf.rlnRelay = Opt.some(twnNetworkConf.rlnRelay)
|
||||
|
||||
info "Starting node"
|
||||
var waku = (waitFor Waku.new(conf)).valueOr:
|
||||
|
||||
@ -51,7 +51,7 @@ proc sendThruWaku*(
|
||||
).valueOr:
|
||||
return err("could not append rate limit proof to the message: " & error)
|
||||
|
||||
(await self.waku.node.publish(some(DefaultPubsubTopic), message)).isOkOr:
|
||||
(await self.waku.node.publish(Opt.some(DefaultPubsubTopic), message)).isOkOr:
|
||||
return err("failed to publish message: " & $error)
|
||||
|
||||
info "rate limit proof is appended to the message"
|
||||
@ -182,7 +182,7 @@ proc new*(
|
||||
except CatchableError:
|
||||
error "could not handle SCP message: ", err = getCurrentExceptionMsg()
|
||||
|
||||
waku.node.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), some(handler)).isOkOr:
|
||||
waku.node.subscribe((kind: PubsubSub, topic: DefaultPubsubTopic), Opt.some(handler)).isOkOr:
|
||||
error "could not subscribe to pubsub topic: ", err = $error
|
||||
return err("could not subscribe to pubsub topic: " & $error)
|
||||
return ok(SCP)
|
||||
|
||||
@ -1,22 +1,20 @@
|
||||
import std/[times, options]
|
||||
import std/times
|
||||
import confutils, chronicles, chronos, results
|
||||
|
||||
import logos_delivery/waku/[waku_core, common/protobuf]
|
||||
import libp2p/protobuf/minprotobuf
|
||||
|
||||
export
|
||||
times, options, confutils, chronicles, chronos, results, waku_core, protobuf,
|
||||
minprotobuf
|
||||
export times, confutils, chronicles, chronos, results, waku_core, protobuf, minprotobuf
|
||||
|
||||
type SerializedKey* = seq[byte]
|
||||
|
||||
type WakuStealthCommitmentMsg* = object
|
||||
request*: bool
|
||||
spendingPubKey*: Option[SerializedKey]
|
||||
viewingPubKey*: Option[SerializedKey]
|
||||
ephemeralPubKey*: Option[SerializedKey]
|
||||
stealthCommitment*: Option[SerializedKey]
|
||||
viewTag*: Option[uint64]
|
||||
spendingPubKey*: Opt[SerializedKey]
|
||||
viewingPubKey*: Opt[SerializedKey]
|
||||
ephemeralPubKey*: Opt[SerializedKey]
|
||||
stealthCommitment*: Opt[SerializedKey]
|
||||
viewTag*: Opt[uint64]
|
||||
|
||||
proc decode*(T: type WakuStealthCommitmentMsg, buffer: seq[byte]): ProtoResult[T] =
|
||||
var msg = WakuStealthCommitmentMsg()
|
||||
@ -29,20 +27,20 @@ proc decode*(T: type WakuStealthCommitmentMsg, buffer: seq[byte]): ProtoResult[T
|
||||
discard ?pb.getField(2, spendingPubKey)
|
||||
msg.spendingPubKey =
|
||||
if spendingPubKey.len > 0:
|
||||
some(spendingPubKey)
|
||||
Opt.some(spendingPubKey)
|
||||
else:
|
||||
none(SerializedKey)
|
||||
Opt.none(SerializedKey)
|
||||
var viewingPubKey = newSeq[byte]()
|
||||
discard ?pb.getField(3, viewingPubKey)
|
||||
msg.viewingPubKey =
|
||||
if viewingPubKey.len > 0:
|
||||
some(viewingPubKey)
|
||||
Opt.some(viewingPubKey)
|
||||
else:
|
||||
none(SerializedKey)
|
||||
Opt.none(SerializedKey)
|
||||
|
||||
if msg.spendingPubKey.isSome() and msg.viewingPubKey.isSome():
|
||||
msg.stealthCommitment = none(SerializedKey)
|
||||
msg.viewTag = none(uint64)
|
||||
msg.stealthCommitment = Opt.none(SerializedKey)
|
||||
msg.viewTag = Opt.none(uint64)
|
||||
return ok(msg)
|
||||
if msg.spendingPubKey.isSome() and msg.viewingPubKey.isNone():
|
||||
return err(ProtoError.RequiredFieldMissing)
|
||||
@ -55,25 +53,25 @@ proc decode*(T: type WakuStealthCommitmentMsg, buffer: seq[byte]): ProtoResult[T
|
||||
discard ?pb.getField(4, stealthCommitment)
|
||||
msg.stealthCommitment =
|
||||
if stealthCommitment.len > 0:
|
||||
some(stealthCommitment)
|
||||
Opt.some(stealthCommitment)
|
||||
else:
|
||||
none(SerializedKey)
|
||||
Opt.none(SerializedKey)
|
||||
|
||||
var ephemeralPubKey = newSeq[byte]()
|
||||
discard ?pb.getField(5, ephemeralPubKey)
|
||||
msg.ephemeralPubKey =
|
||||
if ephemeralPubKey.len > 0:
|
||||
some(ephemeralPubKey)
|
||||
Opt.some(ephemeralPubKey)
|
||||
else:
|
||||
none(SerializedKey)
|
||||
Opt.none(SerializedKey)
|
||||
|
||||
var viewTag: uint64
|
||||
discard ?pb.getField(6, viewTag)
|
||||
msg.viewTag =
|
||||
if viewTag != 0:
|
||||
some(viewTag)
|
||||
Opt.some(viewTag)
|
||||
else:
|
||||
none(uint64)
|
||||
Opt.none(uint64)
|
||||
|
||||
if msg.stealthCommitment.isNone() and msg.viewTag.isNone() and
|
||||
msg.ephemeralPubKey.isNone():
|
||||
@ -86,8 +84,8 @@ proc decode*(T: type WakuStealthCommitmentMsg, buffer: seq[byte]): ProtoResult[T
|
||||
return err(ProtoError.RequiredFieldMissing)
|
||||
|
||||
if msg.stealthCommitment.isSome() and msg.viewTag.isSome():
|
||||
msg.spendingPubKey = none(SerializedKey)
|
||||
msg.viewingPubKey = none(SerializedKey)
|
||||
msg.spendingPubKey = Opt.none(SerializedKey)
|
||||
msg.viewingPubKey = Opt.none(SerializedKey)
|
||||
|
||||
ok(msg)
|
||||
|
||||
@ -118,8 +116,8 @@ proc constructRequest*(
|
||||
): WakuStealthCommitmentMsg =
|
||||
WakuStealthCommitmentMsg(
|
||||
request: true,
|
||||
spendingPubKey: some(spendingPubKey),
|
||||
viewingPubKey: some(viewingPubKey),
|
||||
spendingPubKey: Opt.some(spendingPubKey),
|
||||
viewingPubKey: Opt.some(viewingPubKey),
|
||||
)
|
||||
|
||||
proc constructResponse*(
|
||||
@ -127,7 +125,7 @@ proc constructResponse*(
|
||||
): WakuStealthCommitmentMsg =
|
||||
WakuStealthCommitmentMsg(
|
||||
request: false,
|
||||
stealthCommitment: some(stealthCommitment),
|
||||
ephemeralPubKey: some(ephemeralPubKey),
|
||||
viewTag: some(viewTag),
|
||||
stealthCommitment: Opt.some(stealthCommitment),
|
||||
ephemeralPubKey: Opt.some(ephemeralPubKey),
|
||||
viewTag: Opt.some(viewTag),
|
||||
)
|
||||
|
||||
@ -30,6 +30,21 @@ proc logosdelivery_channel_create(
|
||||
|
||||
return ok(string(id))
|
||||
|
||||
proc logosdelivery_channel_exists(
|
||||
ctx: ptr FFIContext[LogosDelivery],
|
||||
callback: FFICallBack,
|
||||
userData: pointer,
|
||||
channelIdStr: cstring,
|
||||
) {.ffi.} =
|
||||
## Returns `"true"` or `"false"`; a missing channel is not an error.
|
||||
requireInitializedNode(ctx, "ChannelExists"):
|
||||
return err(errMsg)
|
||||
|
||||
requireChannels(ctx, "ChannelExists"):
|
||||
return err(errMsg)
|
||||
|
||||
return ok($ctx.myLib[].reliableChannelManager.channelExists(ChannelId($channelIdStr)))
|
||||
|
||||
proc logosdelivery_channel_send(
|
||||
ctx: ptr FFIContext[LogosDelivery],
|
||||
callback: FFICallBack,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import std/[json, sugar, options]
|
||||
import std/[json, sugar]
|
||||
import chronos, chronicles, results, ffi
|
||||
import
|
||||
logos_delivery,
|
||||
@ -24,17 +24,17 @@ func fromJsonNode(jsonContent: JsonNode): Result[StoreQueryRequest, string] =
|
||||
|
||||
let pubsubTopic =
|
||||
if jsonContent.contains("pubsubTopic"):
|
||||
some(jsonContent["pubsubTopic"].getStr())
|
||||
Opt.some(jsonContent["pubsubTopic"].getStr())
|
||||
else:
|
||||
none(string)
|
||||
Opt.none(string)
|
||||
|
||||
let paginationCursor =
|
||||
if jsonContent.contains("paginationCursor"):
|
||||
let hash = jsonContent["paginationCursor"].getStr().hexToHash().valueOr:
|
||||
return err("Failed converting paginationCursor hex string to bytes: " & error)
|
||||
some(hash)
|
||||
Opt.some(hash)
|
||||
else:
|
||||
none(WakuMessageHash)
|
||||
Opt.none(WakuMessageHash)
|
||||
|
||||
let paginationForwardBool = jsonContent["paginationForward"].getBool()
|
||||
let paginationForward =
|
||||
@ -42,9 +42,9 @@ func fromJsonNode(jsonContent: JsonNode): Result[StoreQueryRequest, string] =
|
||||
|
||||
let paginationLimit =
|
||||
if jsonContent.contains("paginationLimit"):
|
||||
some(uint64(jsonContent["paginationLimit"].getInt()))
|
||||
Opt.some(uint64(jsonContent["paginationLimit"].getInt()))
|
||||
else:
|
||||
none(uint64)
|
||||
Opt.none(uint64)
|
||||
|
||||
let startTime = ?jsonContent.getProtoInt64("timeStart")
|
||||
let endTime = ?jsonContent.getProtoInt64("timeEnd")
|
||||
|
||||
@ -86,6 +86,13 @@ extern "C"
|
||||
const char *contentTopic,
|
||||
const char *senderId);
|
||||
|
||||
// Check whether a reliable channel is currently open. Returns "true" or
|
||||
// "false"; an unknown channel id is not an error.
|
||||
int logosdelivery_channel_exists(void *ctx,
|
||||
FFICallBack callback,
|
||||
void *userData,
|
||||
const char *channelId);
|
||||
|
||||
// Send a message on a reliable channel.
|
||||
// messageJson: { "payload": "base64-encoded-payload", "ephemeral": false }
|
||||
// Returns a request ID that can be used to track delivery.
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import std/[atomics, options, macros]
|
||||
import std/[atomics, macros]
|
||||
import chronicles, chronos, chronos/threadsync, ffi
|
||||
import
|
||||
logos_delivery/waku/waku_core/message/message,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import std/[json, options, strutils]
|
||||
import std/[json, strutils]
|
||||
import results
|
||||
|
||||
proc getProtoInt64*(node: JsonNode, key: string): Result[Option[int64], string] =
|
||||
proc getProtoInt64*(node: JsonNode, key: string): Result[Opt[int64], string] =
|
||||
try:
|
||||
let (value, ok) =
|
||||
if node.hasKey(key):
|
||||
@ -13,8 +13,8 @@ proc getProtoInt64*(node: JsonNode, key: string): Result[Option[int64], string]
|
||||
(0, false)
|
||||
|
||||
if ok:
|
||||
return ok(some(value))
|
||||
return ok(Opt.some(value))
|
||||
|
||||
return ok(none(int64))
|
||||
return ok(Opt.none(int64))
|
||||
except CatchableError:
|
||||
return err("Invalid int64 value in `" & key & "`")
|
||||
|
||||
@ -65,7 +65,7 @@ requires "https://github.com/logos-messaging/nim-ffi#v0.1.3"
|
||||
|
||||
requires "https://github.com/logos-messaging/nim-sds.git#b12f5ee07c5b764303b51fb948b32a4ade1de3b5"
|
||||
|
||||
requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v3.1.4"
|
||||
requires "https://github.com/NagyZoltanPeter/nim-brokers.git#v3.3.0"
|
||||
|
||||
requires "https://github.com/vacp2p/nim-lsquic.git#v0.5.1"
|
||||
requires "https://github.com/vacp2p/nim-jwt.git#057ec95eb5af0eea9c49bfe9025b3312c95dc5f2"
|
||||
@ -388,6 +388,10 @@ task wakunode2, "Build Waku v2 cli node":
|
||||
let name = "wakunode2"
|
||||
buildBinary name, "apps/wakunode2/", " -d:chronicles_log_level=TRACE "
|
||||
|
||||
task logosdeliverynode, "Build Logos Delivery cli node":
|
||||
let name = "logosdeliverynode"
|
||||
buildBinary name, "apps/logos_delivery_node/", " -d:chronicles_log_level=TRACE "
|
||||
|
||||
task benchmarks, "Some benchmarks":
|
||||
let name = "benchmarks"
|
||||
buildBinary name, "apps/benchmarks/", "-p:../.."
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import std/options
|
||||
import results
|
||||
|
||||
type ReliableChannelManagerConf* = object
|
||||
## All-`Option` partial; unset fields fall back to `createReliableChannel` defaults.
|
||||
segmentationEnableReedSolomon*: Option[bool]
|
||||
## All-`Opt` partial; unset fields fall back to `createReliableChannel` defaults.
|
||||
segmentationEnableReedSolomon*: Opt[bool]
|
||||
## Add Reed-Solomon parity segments for recovery of lost segments.
|
||||
segmentationSegmentSizeBytes*: Option[int] ## Maximum segment size in bytes.
|
||||
sdsAcknowledgementTimeoutMs*: Option[int]
|
||||
segmentationSegmentSizeBytes*: Opt[int] ## Maximum segment size in bytes.
|
||||
sdsAcknowledgementTimeoutMs*: Opt[int]
|
||||
## Time to wait before retransmitting an unacknowledged message.
|
||||
sdsMaxRetransmissions*: Option[int]
|
||||
sdsMaxRetransmissions*: Opt[int]
|
||||
## Maximum retransmission attempts before delivery fails.
|
||||
sdsCausalHistorySize*: Option[int] ## Number of message ids kept in causal history.
|
||||
rateLimitEnabled*: Option[bool] ## Enable rate limiting.
|
||||
rateLimitEpochPeriodSec*: Option[int] ## Rate-limit epoch length in seconds.
|
||||
rateLimitMessagesPerEpoch*: Option[int] ## Messages allowed per rate-limit epoch.
|
||||
sdsCausalHistorySize*: Opt[int] ## Number of message ids kept in causal history.
|
||||
rateLimitEnabled*: Opt[bool] ## Enable rate limiting.
|
||||
rateLimitEpochPeriodSec*: Opt[int] ## Rate-limit epoch length in seconds.
|
||||
rateLimitMessagesPerEpoch*: Opt[int] ## Messages allowed per rate-limit epoch.
|
||||
|
||||
@ -1,38 +1,45 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import results
|
||||
|
||||
import logos_delivery/api/conf/modes
|
||||
import logos_delivery/api/conf/messaging_conf
|
||||
import logos_delivery/api/conf/channels_conf
|
||||
|
||||
export options, messaging_conf, channels_conf
|
||||
export modes, messaging_conf, channels_conf
|
||||
|
||||
type LogosDeliveryConf* = object
|
||||
## Aggregates the per-layer config objects. A layer is mounted iff its config
|
||||
## is present.
|
||||
kernelConf*: KernelConf
|
||||
messagingConf*: Option[MessagingClientConf]
|
||||
channelsConf*: Option[ReliableChannelManagerConf]
|
||||
messagingConf*: Opt[MessagingClientConf]
|
||||
channelsConf*: Opt[ReliableChannelManagerConf]
|
||||
|
||||
proc init*(T: type LogosDeliveryConf, kernelConf: KernelConf): LogosDeliveryConf =
|
||||
return LogosDeliveryConf(kernelConf: kernelConf)
|
||||
|
||||
proc init*(
|
||||
T: type LogosDeliveryConf,
|
||||
entryLayer: EntryLayer = EntryLayer.channels,
|
||||
mode: LogosDeliveryMode,
|
||||
preset: string,
|
||||
messagingOverrides: MessagingClientConf,
|
||||
channelsOverrides: ReliableChannelManagerConf,
|
||||
): ConfResult[LogosDeliveryConf] =
|
||||
## Structured (preset + overrides) entry. Only `messaging` / `channels` layers
|
||||
## reach here; the `kernel` layer uses `init(kernelConf)` (raw, mode ignored).
|
||||
let merged = merge(?resolvePreset(preset), messagingOverrides)
|
||||
var kernelConf = ?toWakuNodeConf(merged, mode)
|
||||
kernelConf.preset = preset
|
||||
return ok(
|
||||
LogosDeliveryConf(
|
||||
kernelConf: KernelConf(kernelConf),
|
||||
messagingConf: some(merged),
|
||||
channelsConf: some(channelsOverrides),
|
||||
messagingConf: Opt.some(merged),
|
||||
channelsConf:
|
||||
if entryLayer == EntryLayer.channels:
|
||||
Opt.some(channelsOverrides)
|
||||
else:
|
||||
Opt.none(ReliableChannelManagerConf),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[json, options, strutils, tables]
|
||||
import std/[json, strutils, tables]
|
||||
import results
|
||||
|
||||
import tools/confutils/conf_from_json
|
||||
@ -8,6 +8,7 @@ import logos_delivery/api/conf/logos_delivery_conf
|
||||
|
||||
const
|
||||
# Lowercased, since `collectJsonFields` keys the object case-insensitively.
|
||||
KeyEntryLayer = "entrylayer"
|
||||
KeyMode = "mode"
|
||||
KeyPreset = "preset"
|
||||
KeyKernelConf = "kernelconf"
|
||||
@ -24,10 +25,21 @@ proc parseMode(s: string): Result[LogosDeliveryMode, string] =
|
||||
return ok(LogosDeliveryMode.Core)
|
||||
of "edge":
|
||||
return ok(LogosDeliveryMode.Edge)
|
||||
of "fleet":
|
||||
return ok(LogosDeliveryMode.Fleet)
|
||||
else:
|
||||
return err("invalid mode: '" & s & "' (expected 'Core', 'Edge' or 'Fleet')")
|
||||
return err("invalid mode: '" & s & "' (expected 'Core' or 'Edge')")
|
||||
|
||||
proc parseEntryLayer(s: string): Result[EntryLayer, string] =
|
||||
case s.strip().toLowerAscii()
|
||||
of "kernel":
|
||||
return ok(EntryLayer.kernel)
|
||||
of "messaging":
|
||||
return ok(EntryLayer.messaging)
|
||||
of "channels":
|
||||
return ok(EntryLayer.channels)
|
||||
else:
|
||||
return err(
|
||||
"invalid entryLayer: '" & s & "' (expected 'kernel', 'messaging' or 'channels')"
|
||||
)
|
||||
|
||||
proc parseOverrides[T](defaults: T, node: JsonNode, label: string): Result[T, string] =
|
||||
## Parse the JSON object `node` as overrides on top of `defaults`.
|
||||
@ -82,8 +94,8 @@ proc parseFlatConf(
|
||||
return ok(
|
||||
LogosDeliveryConf(
|
||||
kernelConf: KernelConf(kernel),
|
||||
messagingConf: some(messaging),
|
||||
channelsConf: some(ReliableChannelManagerConf()),
|
||||
messagingConf: Opt.some(messaging),
|
||||
channelsConf: Opt.some(ReliableChannelManagerConf()),
|
||||
)
|
||||
)
|
||||
|
||||
@ -106,16 +118,25 @@ proc parseLogosDeliveryConf*(jsonStr: string): ConfResult[LogosDeliveryConf] =
|
||||
mode = ?parseMode(v.getStr())
|
||||
top.del(KeyMode)
|
||||
|
||||
if mode == LogosDeliveryMode.Fleet:
|
||||
# Kernel-only: a raw kernelConf and no upper layers.
|
||||
var entryLayer = EntryLayer.channels
|
||||
if top.hasKey(KeyEntryLayer):
|
||||
let (_, v) = top.getOrDefault(KeyEntryLayer)
|
||||
if v.kind != JString:
|
||||
return err("entryLayer must be a string")
|
||||
entryLayer = ?parseEntryLayer(v.getStr())
|
||||
top.del(KeyEntryLayer)
|
||||
|
||||
if entryLayer == EntryLayer.kernel:
|
||||
# Kernel-only: a raw kernelConf and no upper layers; mode is ignored.
|
||||
if not top.hasKey(KeyKernelConf):
|
||||
return err("fleet mode requires a 'kernelConf' object")
|
||||
return err("kernel entry layer requires a 'kernelConf' object")
|
||||
let (_, v) = top.getOrDefault(KeyKernelConf)
|
||||
let kernel = ?parseOverrides(?defaultWakuNodeConf(), v, "kernelConf")
|
||||
top.del(KeyKernelConf)
|
||||
if top.len > 0:
|
||||
return
|
||||
err(unknownKeysError(top, "fleet mode takes only 'kernelConf'; unexpected"))
|
||||
return err(
|
||||
unknownKeysError(top, "kernel entry layer takes only 'kernelConf'; unexpected")
|
||||
)
|
||||
return ok(LogosDeliveryConf.init(KernelConf(kernel)))
|
||||
|
||||
# [Legacy flat JSON config] A wrapper key marks our structured shape. Otherwise any
|
||||
@ -158,6 +179,12 @@ proc parseLogosDeliveryConf*(jsonStr: string): ConfResult[LogosDeliveryConf] =
|
||||
if top.len > 0:
|
||||
return err(unknownKeysError(top, "Unrecognized configuration option(s) found"))
|
||||
|
||||
return LogosDeliveryConf.init(mode, preset, messagingOverrides, channelsOverrides)
|
||||
return LogosDeliveryConf.init(
|
||||
entryLayer = entryLayer,
|
||||
mode = mode,
|
||||
preset = preset,
|
||||
messagingOverrides = messagingOverrides,
|
||||
channelsOverrides = channelsOverrides,
|
||||
)
|
||||
|
||||
{.pop.}
|
||||
|
||||
@ -1,59 +1,60 @@
|
||||
import std/options
|
||||
import std/net
|
||||
import results
|
||||
import libp2p/crypto/crypto
|
||||
import results, libp2p/crypto/crypto
|
||||
|
||||
import logos_delivery/api/conf/kernel_conf
|
||||
import logos_delivery/waku/common/logging
|
||||
import logos_delivery/waku/factory/networks_config
|
||||
import logos_delivery/messaging/rate_limit_manager/rate_limit_manager
|
||||
|
||||
export kernel_conf
|
||||
export kernel_conf, rate_limit_manager
|
||||
|
||||
type LogosDeliveryMode* {.pure.} = enum
|
||||
Edge # client-only node
|
||||
Core # full service node
|
||||
Fleet # kernel-only node from a raw kernel config
|
||||
# `LogosDeliveryMode` and `EntryLayer` are defined at the leaf (`cli_args`) so
|
||||
# they can appear on `WakuNodeConf`; re-exported here via `kernel_conf`.
|
||||
|
||||
type MessagingClientConf* = object
|
||||
clusterId* {.name: "cluster-id".}: Option[uint16] ## Network cluster id.
|
||||
numShardsInCluster* {.name: "num-shards-in-network".}: Option[uint16]
|
||||
clusterId* {.name: "cluster-id".}: Opt[uint16] ## Network cluster id.
|
||||
numShardsInCluster* {.name: "num-shards-in-network".}: Opt[uint16]
|
||||
## Number of shards in the cluster.
|
||||
p2pTcpPort* {.name: "tcp-port".}: Option[Port] ## TCP listening port.
|
||||
discv5UdpPort* {.name: "discv5-udp-port".}: Option[Port] ## discv5 UDP port.
|
||||
websocketSupport* {.name: "websocket-support".}: Option[bool]
|
||||
p2pTcpPort* {.name: "tcp-port".}: Opt[Port] ## TCP listening port.
|
||||
discv5UdpPort* {.name: "discv5-udp-port".}: Opt[Port] ## discv5 UDP port.
|
||||
websocketSupport* {.name: "websocket-support".}: Opt[bool]
|
||||
## Enable the websocket transport.
|
||||
websocketPort* {.name: "websocket-port".}: Option[Port] ## Websocket listening port.
|
||||
quicSupport* {.name: "quic-support".}: Option[bool] ## Enable the QUIC transport.
|
||||
quicPort* {.name: "quic-port".}: Option[Port] ## QUIC (UDP) listening port.
|
||||
listenIpv4* {.name: "listen-address".}: Option[IpAddress] ## Inbound bind address.
|
||||
maxMessageSize* {.name: "max-msg-size".}: Option[string]
|
||||
websocketPort* {.name: "websocket-port".}: Opt[Port] ## Websocket listening port.
|
||||
quicSupport* {.name: "quic-support".}: Opt[bool] ## Enable the QUIC transport.
|
||||
quicPort* {.name: "quic-port".}: Opt[Port] ## QUIC (UDP) listening port.
|
||||
listenIpv4* {.name: "listen-address".}: Opt[IpAddress] ## Inbound bind address.
|
||||
maxMessageSize* {.name: "max-msg-size".}: Opt[string]
|
||||
## Maximum accepted message size (e.g. "150 KiB").
|
||||
entryNodes* {.name: "entry-node".}: Option[seq[string]]
|
||||
entryNodes* {.name: "entry-node".}: Opt[seq[string]]
|
||||
## Bootstrap / connectivity nodes (enrtree or multiaddr).
|
||||
ethRpcEndpoints* {.name: "rln-relay-eth-client-address".}: Option[seq[EthRpcUrl]]
|
||||
ethRpcEndpoints* {.name: "rln-relay-eth-client-address".}: Opt[seq[EthRpcUrl]]
|
||||
## Ethereum RPC endpoints (required for RLN validation); multiple for fail-over.
|
||||
rlnContractAddress* {.name: "rln-relay-eth-contract-address".}: Option[string]
|
||||
rlnContractAddress* {.name: "rln-relay-eth-contract-address".}: Opt[string]
|
||||
## RLN contract address; when set, RLN validation is enabled.
|
||||
rlnChainId* {.name: "rln-relay-chain-id".}: Option[uint]
|
||||
rlnChainId* {.name: "rln-relay-chain-id".}: Opt[uint]
|
||||
## Chain id the RLN contract is deployed on.
|
||||
rlnEpochSizeSec* {.name: "rln-relay-epoch-sec".}: Option[uint]
|
||||
rlnEpochSizeSec* {.name: "rln-relay-epoch-sec".}: Opt[uint]
|
||||
## RLN epoch size, in seconds.
|
||||
reliabilityEnabled* {.name: "reliability".}: Option[bool]
|
||||
reliabilityEnabled* {.name: "reliability".}: Opt[bool]
|
||||
## Enable store-based send reliability.
|
||||
store*: Option[bool] ## Enable the store protocol.
|
||||
storenode* {.name: "storenode".}: Option[string]
|
||||
storeMessageDbUrl* {.name: "store-message-db-url".}: Option[string]
|
||||
store*: Opt[bool] ## Enable the store protocol.
|
||||
storenode* {.name: "storenode".}: Opt[string]
|
||||
storeMessageDbUrl* {.name: "store-message-db-url".}: Opt[string]
|
||||
## Database connection URL for the store service's persistent storage.
|
||||
storeMessageRetentionPolicy* {.name: "store-message-retention-policy".}:
|
||||
Option[string] ## Store retention policy (e.g. "time:3600;size:1GB").
|
||||
storeMaxNumDbConnections* {.name: "store-max-num-db-connections".}: Option[int]
|
||||
storeMessageRetentionPolicy* {.name: "store-message-retention-policy".}: Opt[string]
|
||||
## Store retention policy (e.g. "time:3600;size:1GB").
|
||||
storeMaxNumDbConnections* {.name: "store-max-num-db-connections".}: Opt[int]
|
||||
## Maximum number of simultaneous store database connections.
|
||||
logLevel* {.name: "log-level".}: Option[logging.LogLevel]
|
||||
logLevel* {.name: "log-level".}: Opt[logging.LogLevel]
|
||||
## Process log level (TRACE..FATAL); applied by the kernel on node creation.
|
||||
logFormat* {.name: "log-format".}: Option[logging.LogFormat]
|
||||
logFormat* {.name: "log-format".}: Opt[logging.LogFormat]
|
||||
## Process log format (TEXT or JSON); applied by the kernel on node creation.
|
||||
nodeKey* {.name: "nodekey".}: Option[crypto.PrivateKey]
|
||||
nodeKey* {.name: "nodekey".}: Opt[crypto.PrivateKey]
|
||||
## P2P node private key (64-char hex): stable identity / peerId across restarts.
|
||||
rateLimit*: Opt[RateLimitConfig]
|
||||
## Per-epoch message rate limit enforced by the send service. `Opt` like
|
||||
## every other field so `merge` propagates a caller's override; unset falls
|
||||
## back to `DefaultRateLimitConfig` (rate limiting disabled).
|
||||
|
||||
proc applyMode*(conf: var WakuNodeConf, mode: LogosDeliveryMode): ConfResult[void] =
|
||||
## Sets the protocol flags implied by the mode.
|
||||
@ -62,7 +63,7 @@ proc applyMode*(conf: var WakuNodeConf, mode: LogosDeliveryMode): ConfResult[voi
|
||||
conf.relay = true
|
||||
conf.filter = true
|
||||
conf.lightpush = true
|
||||
conf.discv5Discovery = some(true)
|
||||
conf.discv5Discovery = Opt.some(true)
|
||||
conf.peerExchange = true
|
||||
conf.rendezvous = true
|
||||
of LogosDeliveryMode.Edge:
|
||||
@ -71,9 +72,6 @@ proc applyMode*(conf: var WakuNodeConf, mode: LogosDeliveryMode): ConfResult[voi
|
||||
conf.filter = false
|
||||
conf.lightpush = false
|
||||
conf.store = false
|
||||
of LogosDeliveryMode.Fleet:
|
||||
return
|
||||
err("fleet mode takes a raw kernel config; use LogosDelivery.new(kernelConf)")
|
||||
return ok()
|
||||
|
||||
proc toWakuNodeConf*(
|
||||
@ -82,6 +80,10 @@ proc toWakuNodeConf*(
|
||||
## Mode sets the protocol flags; set fields map to their kernel counterpart.
|
||||
var conf = ?defaultWakuNodeConf()
|
||||
?applyMode(conf, mode)
|
||||
# Keep the `mode` field consistent with the applied flags so a later
|
||||
# `LogosDelivery.new(WakuNodeConf)` re-application is idempotent instead of
|
||||
# clobbering these flags with the field's default (`Core`).
|
||||
conf.mode = mode
|
||||
|
||||
if self.store.isSome():
|
||||
conf.store = self.store.get()
|
||||
@ -108,11 +110,11 @@ proc toWakuNodeConf*(
|
||||
conf.ethClientUrls = self.ethRpcEndpoints.get()
|
||||
if self.rlnContractAddress.isSome():
|
||||
conf.rlnRelayEthContractAddress = self.rlnContractAddress.get()
|
||||
conf.rlnRelay = some(true)
|
||||
conf.rlnRelay = Opt.some(true)
|
||||
if self.rlnChainId.isSome():
|
||||
conf.rlnRelayChainId = self.rlnChainId.get()
|
||||
if self.rlnEpochSizeSec.isSome():
|
||||
conf.rlnEpochSizeSec = some(self.rlnEpochSizeSec.get().uint64)
|
||||
conf.rlnEpochSizeSec = Opt.some(self.rlnEpochSizeSec.get().uint64)
|
||||
if self.logLevel.isSome():
|
||||
conf.logLevel = self.logLevel.get()
|
||||
if self.logFormat.isSome():
|
||||
@ -132,7 +134,7 @@ proc toWakuNodeConf*(
|
||||
proc merge*(base, overrides: MessagingClientConf): MessagingClientConf =
|
||||
var m = base
|
||||
for _, mField, oField in fieldPairs(m, overrides):
|
||||
when oField is Option:
|
||||
when oField is Opt:
|
||||
if oField.isSome():
|
||||
mField = oField
|
||||
return m
|
||||
@ -140,8 +142,8 @@ proc merge*(base, overrides: MessagingClientConf): MessagingClientConf =
|
||||
proc resolvePreset*(preset: string): ConfResult[MessagingClientConf] =
|
||||
## Preset to messaging-only fields. Kernel-mirrored fields stay unset; the
|
||||
## kernel resolves those from `conf.preset`.
|
||||
let npcOpt = ?toNetworkPresetConf(preset, none(uint16))
|
||||
let npcOpt = ?toNetworkPresetConf(preset, Opt.none(uint16))
|
||||
if npcOpt.isNone():
|
||||
return ok(MessagingClientConf())
|
||||
let npc = npcOpt.get()
|
||||
return ok(MessagingClientConf(reliabilityEnabled: some(npc.p2pReliability)))
|
||||
return ok(MessagingClientConf(reliabilityEnabled: Opt.some(npc.p2pReliability)))
|
||||
|
||||
18
logos_delivery/api/conf/modes.nim
Normal file
18
logos_delivery/api/conf/modes.nim
Normal file
@ -0,0 +1,18 @@
|
||||
## Leaf module for the app-level mode / entry-layer enums.
|
||||
##
|
||||
## These appear on `WakuNodeConf` (in the leaf `tools/confutils/cli_args`), so
|
||||
## they must live in a module that `cli_args` can import without a cycle — i.e.
|
||||
## a module that imports nothing from the config/api layers. `logos_delivery_conf`
|
||||
## re-exports them so consumers still get them from there.
|
||||
|
||||
type LogosDeliveryMode* {.pure.} = enum
|
||||
## Drives the kernel-internal protocol mountings. Applied only for the
|
||||
## `messaging` / `channels` entry layers; ignored when `entryLayer == kernel`.
|
||||
Edge # client-only node
|
||||
Core # full service node
|
||||
|
||||
type EntryLayer* {.pure.} = enum
|
||||
## Selects which API layer `LogosDelivery` instantiates.
|
||||
kernel # transport kernel only; ignores `mode` and uses the config as-is
|
||||
messaging # kernel + messaging client
|
||||
channels # kernel + messaging + reliable channels
|
||||
@ -10,6 +10,7 @@ type ReliableChannelApi* = concept c
|
||||
createReliableChannel(
|
||||
c, channelId = ChannelId, contentTopic = ContentTopic, senderId = SdsParticipantID
|
||||
) is Result[ChannelId, string]
|
||||
channelExists(c, channelId = ChannelId) is bool
|
||||
closeChannel(c, channelId = ChannelId) is Future[Result[void, string]]
|
||||
send(c, channelId = ChannelId, appPayload = seq[byte]) is
|
||||
Future[Result[RequestId, string]]
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import libp2p/crypto/crypto
|
||||
{.push raises: [].}
|
||||
|
||||
import bearssl/rand, std/times, chronos
|
||||
import libp2p/crypto/crypto, bearssl/rand, std/times, chronos
|
||||
import stew/byteutils
|
||||
import logos_delivery/waku/utils/requests as request_utils
|
||||
import logos_delivery/waku/waku_core/[topics/content_topic, message/message, time]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
## Reliable Channel layer API — channel lifecycle
|
||||
## (createReliableChannel / closeChannel).
|
||||
import std/[options, tables]
|
||||
import std/tables
|
||||
import results, chronos, chronicles
|
||||
|
||||
import logos_delivery/api/types
|
||||
@ -15,17 +15,17 @@ const SdsJobId = "sds"
|
||||
## One persistency job shared by every channel's SDS state; rows are
|
||||
## keyed by channelId.
|
||||
|
||||
proc sdsPersistence(): Option[Persistence] =
|
||||
proc sdsPersistence(): Opt[Persistence] =
|
||||
## SDS backend from the Persistency singleton; memory-only fallback when
|
||||
## it is unavailable (e.g. unit tests).
|
||||
let p = Persistency.instance().valueOr:
|
||||
info "SDS persistence disabled, running memory-only", reason = $error
|
||||
return none(Persistence)
|
||||
return Opt.none(Persistence)
|
||||
let job = p.openJob(SdsJobId).valueOr:
|
||||
warn "SDS persistence disabled, could not open persistency job",
|
||||
jobId = SdsJobId, reason = $error
|
||||
return none(Persistence)
|
||||
return some(newSdsPersistence(job))
|
||||
return Opt.none(Persistence)
|
||||
return Opt.some(newSdsPersistence(job))
|
||||
|
||||
proc createReliableChannel*(
|
||||
self: ReliableChannelManager,
|
||||
@ -51,15 +51,6 @@ proc createReliableChannel*(
|
||||
causalHistorySize: cc.sdsCausalHistorySize.get(DefaultCausalHistorySize),
|
||||
persistence: sdsPersistence(),
|
||||
)
|
||||
let rateConfig = RateLimitConfig(
|
||||
# Setting a rate-limit parameter implies enabling; an explicit
|
||||
# rateLimitEnabled still wins.
|
||||
enabled: cc.rateLimitEnabled.get(
|
||||
cc.rateLimitEpochPeriodSec.isSome() or cc.rateLimitMessagesPerEpoch.isSome()
|
||||
),
|
||||
epochPeriodSec: cc.rateLimitEpochPeriodSec.get(DefaultEpochPeriodSec),
|
||||
messagesPerEpoch: cc.rateLimitMessagesPerEpoch.get(DefaultMessagesPerEpoch),
|
||||
)
|
||||
|
||||
let chn = ReliableChannel.new(
|
||||
channelId = channelId,
|
||||
@ -67,13 +58,18 @@ proc createReliableChannel*(
|
||||
senderId = senderId,
|
||||
segConfig = segConfig,
|
||||
sdsConfig = sdsConfig,
|
||||
rateConfig = rateConfig,
|
||||
brokerCtx = self.brokerCtx,
|
||||
)
|
||||
|
||||
self.channels[channelId] = chn
|
||||
return ok(channelId)
|
||||
|
||||
proc channelExists*(self: ReliableChannelManager, channelId: ChannelId): bool =
|
||||
## True while the channel is held by the manager, i.e. between a successful
|
||||
## `createReliableChannel` and `closeChannel`. Persisted SDS state for a
|
||||
## closed channel does not count as existing.
|
||||
return self.channels.hasKey(channelId)
|
||||
|
||||
proc closeChannel*(
|
||||
self: ReliableChannelManager, channelId: ChannelId
|
||||
): Future[Result[void, string]] {.async: (raises: []).} =
|
||||
|
||||
@ -14,7 +14,7 @@ proc send*(
|
||||
): Future[Result[RequestId, string]] {.async: (raises: []).} =
|
||||
## Spec-level entry point. Looks the channel up by id and delegates
|
||||
## to `ReliableChannel.send`, which exposes the visible pipeline
|
||||
## segmentation -> sds -> rate_limit_manager -> encryption.
|
||||
## segmentation -> sds -> encryption -> dispatch.
|
||||
let chn = self.channels.getOrDefault(channelId)
|
||||
if chn.isNil():
|
||||
return err("unknown channel: " & channelId)
|
||||
|
||||
@ -1,80 +0,0 @@
|
||||
## Rate Limit Manager for the Reliable Channel API.
|
||||
##
|
||||
## Tracks messages sent per RLN epoch and delays dispatch when the
|
||||
## limit is approached, ensuring RLN compliance on enforcing relays.
|
||||
##
|
||||
## For the skeleton this is a pass-through: messages are immediately
|
||||
## released as ready-to-send. Real epoch budgeting will be added later.
|
||||
##
|
||||
## See: https://lip.logos.co/messaging/raw/reliable-channel-api.html
|
||||
|
||||
import std/times
|
||||
import message
|
||||
import brokers/event_broker
|
||||
import brokers/broker_context
|
||||
|
||||
export event_broker, broker_context
|
||||
export message.SdsChannelID
|
||||
|
||||
const
|
||||
DefaultEpochPeriodSec* = 600
|
||||
DefaultMessagesPerEpoch* = 1
|
||||
|
||||
EventBroker:
|
||||
## Emitted by `enqueueToSend` carrying the batch of opaque message
|
||||
## blobs that may now leave the rate limiter and continue down the
|
||||
## outgoing pipeline (encryption -> dispatch). Bytes only: the rate
|
||||
## limiter is intentionally agnostic of SDS, so anything serialisable
|
||||
## can flow through it.
|
||||
##
|
||||
## `channelId` lets listeners filter to their own channel, since all
|
||||
## reliable channels share the underlying Waku node's broker context.
|
||||
type ReadyToSendEvent* = ref object
|
||||
channelId*: SdsChannelID
|
||||
msgs*: seq[seq[byte]]
|
||||
|
||||
type
|
||||
RateLimitConfig* = object
|
||||
enabled*: bool ## spec: rate limiting opt-in; SHOULD be true when RLN active
|
||||
epochPeriodSec*: int
|
||||
messagesPerEpoch*: int
|
||||
|
||||
RateLimitManager* = ref object
|
||||
config*: RateLimitConfig
|
||||
queue*: seq[seq[byte]]
|
||||
currentEpochStart*: Time
|
||||
sentInCurrentEpoch*: int
|
||||
channelId*: SdsChannelID ## tag for the emitted `ReadyToSendEvent`
|
||||
brokerCtx: BrokerContext
|
||||
|
||||
proc new*(
|
||||
T: type RateLimitManager,
|
||||
config: RateLimitConfig,
|
||||
channelId: SdsChannelID,
|
||||
brokerCtx: BrokerContext = globalBrokerContext(),
|
||||
): T =
|
||||
return T(
|
||||
config: config,
|
||||
queue: @[],
|
||||
currentEpochStart: getTime(),
|
||||
sentInCurrentEpoch: 0,
|
||||
channelId: channelId,
|
||||
brokerCtx: brokerCtx,
|
||||
)
|
||||
|
||||
proc enqueueToSend*(self: RateLimitManager, msg: seq[byte]) =
|
||||
## Skeleton behaviour: enqueue and immediately release as a single
|
||||
## ready batch. Real per-epoch budgeting will park messages on
|
||||
## `self.queue` and emit only when the budget allows.
|
||||
ReadyToSendEvent.emit(
|
||||
self.brokerCtx, ReadyToSendEvent(channelId: self.channelId, msgs: @[msg])
|
||||
)
|
||||
|
||||
proc dequeueReady*(self: RateLimitManager): seq[seq[byte]] =
|
||||
## Returns the set of queued messages that may be dispatched now
|
||||
## without exceeding the configured rate limit.
|
||||
discard
|
||||
|
||||
proc resetEpoch*(self: RateLimitManager) =
|
||||
self.currentEpochStart = getTime()
|
||||
self.sentInCurrentEpoch = 0
|
||||
@ -1,11 +1,10 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
## Reliable Channel type.
|
||||
##
|
||||
## A `ReliableChannel` orchestrates segmentation, SDS (end-to-end
|
||||
## reliability), optional encryption, and rate-limited dispatch on top
|
||||
## of the Messaging API for a single channel.
|
||||
## reliability), optional encryption, and dispatch on top of the
|
||||
## Messaging API for a single channel.
|
||||
##
|
||||
## Outgoing pipeline: Segment -> SDS -> Rate Limit -> Encrypt -> Dispatch
|
||||
## Outgoing pipeline: Segment -> SDS -> Encrypt -> Dispatch
|
||||
## Incoming pipeline: Decrypt -> SDS -> Reassemble -> Emit event
|
||||
##
|
||||
## Channels are owned by a `ReliableChannelManager`. Lifecycle and send
|
||||
@ -14,9 +13,8 @@ import logos_delivery/waku/compat/option_valueor
|
||||
##
|
||||
## See: https://lip.logos.co/messaging/raw/reliable-channel-api.html
|
||||
|
||||
import std/[options, tables]
|
||||
import results
|
||||
import chronos
|
||||
import std/tables
|
||||
import results, chronos
|
||||
import bearssl/rand
|
||||
import stew/byteutils
|
||||
import libp2p/crypto/crypto as libp2p_crypto
|
||||
@ -31,12 +29,9 @@ import logos_delivery/waku/waku_core/topics
|
||||
|
||||
import ./segmentation/segmentation
|
||||
import ./scalable_data_sync/scalable_data_sync
|
||||
import ./rate_limit_manager/rate_limit_manager
|
||||
import ./encryption/encryption
|
||||
|
||||
export
|
||||
types, reliable_channel_manager_api, segmentation, scalable_data_sync,
|
||||
rate_limit_manager, encryption
|
||||
export types, reliable_channel_manager_api, segmentation, scalable_data_sync, encryption
|
||||
|
||||
const LipWireReliableChannelVersion* = "RELIABLE-CHANNEL-API/1"
|
||||
## Wire-format spec marker for the Reliable Channel layer, as defined
|
||||
@ -53,33 +48,22 @@ type
|
||||
|
||||
ChannelReqState = object
|
||||
## Per channel-level request, tracks how many of its segments are
|
||||
## still queued, in flight, or have terminated. The channel-level
|
||||
## final event fires when `confirmedCount + failedCount` reaches
|
||||
## `totalExpectedSegments` AND no segments are still awaiting dispatch
|
||||
## or in flight.
|
||||
## still in flight or have terminated. The channel-level final event
|
||||
## fires when `confirmedCount + failedCount` reaches
|
||||
## `totalExpectedSegments` AND no segments are still in flight.
|
||||
persistenceReqType: MessagePersistence
|
||||
totalExpectedSegments: int
|
||||
## Total segments produced by `segmentation.performSegmentation`
|
||||
## for this `channelReqId`. Set once in `send`, never mutated.
|
||||
awaitingDispatch: int
|
||||
## Segments enqueued in `rate_limit_manager` but not yet claimed
|
||||
## by `onReadyToSend`. Decremented when `onReadyToSend` picks a
|
||||
## message and assigns it to this `channelReqId`.
|
||||
inflightMessagingIds: seq[RequestId]
|
||||
## Messaging-layer ids minted by the send handler that have not
|
||||
## yet produced a final event. Removed on `MessageSentEvent` / `MessageErrorEvent`.
|
||||
confirmedCount: int
|
||||
failedCount: int
|
||||
|
||||
ChannelReqs = OrderedTable[RequestId, ChannelReqState]
|
||||
ChannelReqs = Table[RequestId, ChannelReqState]
|
||||
## Key: channelReqId (the parent id returned by channel `send`). Value:
|
||||
## per-request state, see `ChannelReqState`.
|
||||
##
|
||||
## `OrderedTable` preserves insertion order, which matches the FIFO
|
||||
## order `rate_limit_manager` re-emits messages in: `onReadyToSend`
|
||||
## routes each segment to the first entry with `awaitingDispatch > 0`,
|
||||
## and that scan is correct precisely because the outer iteration
|
||||
## order matches the order `send` pushed entries.
|
||||
|
||||
ReliableChannel* = ref object
|
||||
## Spec-defined public type. Fields are private so callers cannot
|
||||
@ -91,7 +75,6 @@ type
|
||||
rng: libp2p_crypto.Rng
|
||||
segmentation: SegmentationHandler
|
||||
sdsHandler: SdsHandler
|
||||
rateLimit: RateLimitManager
|
||||
|
||||
channelReqs: ChannelReqs
|
||||
brokerCtx: BrokerContext
|
||||
@ -104,7 +87,6 @@ func init(
|
||||
return ChannelReqState(
|
||||
persistenceReqType: persistenceReqType,
|
||||
totalExpectedSegments: totalExpectedSegments,
|
||||
awaitingDispatch: totalExpectedSegments,
|
||||
inflightMessagingIds: @[],
|
||||
confirmedCount: 0,
|
||||
failedCount: 0,
|
||||
@ -125,8 +107,8 @@ proc stop*(self: ReliableChannel) {.async: (raises: []).} =
|
||||
|
||||
proc tryFinalizeChannelReq(self: ReliableChannel, channelReqId: RequestId) =
|
||||
## Tries to finalize the channel-level request identified by `channelReqId` if
|
||||
## certain conditions are met, i.e., no segments are still awaiting dispatch or in flight,
|
||||
## and the total number of confirmed + failed segments equals the total expected segments.
|
||||
## certain conditions are met, i.e., no segments are still in flight and the
|
||||
## total number of confirmed + failed segments equals the total expected segments.
|
||||
## Therefore, the channel-level request is removed from `self.channelReqs`
|
||||
## and the appropriate final event is emitted.
|
||||
##
|
||||
@ -134,7 +116,7 @@ proc tryFinalizeChannelReq(self: ReliableChannel, channelReqId: RequestId) =
|
||||
if state.totalExpectedSegments == 0:
|
||||
## Either already finalized (and removed) or never inserted.
|
||||
return
|
||||
if state.awaitingDispatch != 0 or state.inflightMessagingIds.len != 0:
|
||||
if state.inflightMessagingIds.len != 0:
|
||||
return
|
||||
if state.confirmedCount + state.failedCount < state.totalExpectedSegments:
|
||||
return
|
||||
@ -156,22 +138,6 @@ proc tryFinalizeChannelReq(self: ReliableChannel, channelReqId: RequestId) =
|
||||
ChannelMessageSentEvent(channelId: self.channelId, requestId: channelReqId),
|
||||
)
|
||||
|
||||
type ClaimedSegment = object
|
||||
channelReqId: RequestId
|
||||
isEphemeral: bool
|
||||
|
||||
proc claimAwaitingChannelReq(self: ReliableChannel): Option[ClaimedSegment] =
|
||||
for channelReqId, state in self.channelReqs.mpairs:
|
||||
if state.awaitingDispatch > 0:
|
||||
state.awaitingDispatch.dec()
|
||||
return some(
|
||||
ClaimedSegment(
|
||||
channelReqId: channelReqId,
|
||||
isEphemeral: state.persistenceReqType == MessagePersistence.Ephemeral,
|
||||
)
|
||||
)
|
||||
return none(ClaimedSegment)
|
||||
|
||||
type MessagingOutcome {.pure.} = enum
|
||||
Sent
|
||||
Failed
|
||||
@ -210,62 +176,61 @@ proc markSegmentInflight(
|
||||
error "unreachable: channelReqId not found in markSegmentInflight",
|
||||
channelReqId = $channelReqId, error = e.msg
|
||||
|
||||
proc onReadyToSend(
|
||||
self: ReliableChannel, readyToSendEvent: ReadyToSendEvent
|
||||
) {.async: (raises: []).} =
|
||||
## Tail of the outgoing pipeline. Invoked from the `ReadyToSendEvent`
|
||||
## listener once `rate_limit_manager` releases a batch of opaque
|
||||
## blobs (already-encoded SDS messages):
|
||||
proc send*(
|
||||
self: ReliableChannel, payload: seq[byte], ephemeral: bool = false
|
||||
): Future[Result[RequestId, string]] {.async: (raises: []).} =
|
||||
## Single application-level send:
|
||||
##
|
||||
## ... -> rate_limit_manager -> [encryption] -> dispatch
|
||||
## segmentation -> sds -> encryption -> dispatch
|
||||
##
|
||||
## For each `m`, the next channelReqId still queued in rate-limit
|
||||
## claims the slot (FIFO across sibling sends). The channelReqId is
|
||||
## captured up front and used as a stable key for every later state
|
||||
## update — no positional index is ever held across an `await`, so
|
||||
## sibling events mutating other entries (or even this one's
|
||||
## `inflightMessagingIds`) cannot corrupt this fiber's view.
|
||||
for m in readyToSendEvent.msgs:
|
||||
let claimed = self.claimAwaitingChannelReq().valueOr:
|
||||
## rate_limit_manager emitted more messages than we have pending —
|
||||
## should not happen given `send` increments `awaitingDispatch`
|
||||
## once per enqueued SDS payload. Drop silently rather than
|
||||
## corrupt state.
|
||||
break
|
||||
let channelReqId = claimed.channelReqId
|
||||
let isEphemeral = claimed.isEphemeral
|
||||
## The returned `RequestId` is the channel-level parent of one-or-more
|
||||
## messaging-layer `RequestId`s; the mapping is held in
|
||||
## `self.channelReqs` until every segment is final.
|
||||
if payload.len == 0:
|
||||
return err("empty payload")
|
||||
|
||||
let channelReqId = RequestId.new(self.rng)
|
||||
let persistenceReqType =
|
||||
if ephemeral: MessagePersistence.Ephemeral else: MessagePersistence.Persistent
|
||||
|
||||
var sdsSegments: seq[seq[byte]]
|
||||
for segmentBytes in self.segmentation.performSegmentation(payload):
|
||||
## Segments arrive already encoded; the segmentation module owns
|
||||
## the wire format so SDS only ever sees opaque bytes.
|
||||
let sdsBytes = (await self.sdsHandler.wrapOutgoing(segmentBytes)).valueOr:
|
||||
return err("SDS wrap failed: " & error)
|
||||
sdsSegments.add(sdsBytes)
|
||||
|
||||
self.channelReqs[channelReqId] =
|
||||
ChannelReqState.init(persistenceReqType, sdsSegments.len)
|
||||
|
||||
for sdsBytes in sdsSegments:
|
||||
## TODO: revisit which fields of the SDS message must be encrypted.
|
||||
## Encrypting the whole encoded blob forces every receiver to attempt
|
||||
## decryption before it can route, which breaks selective dispatch.
|
||||
## Leave routing metadata (channelId, causal-history references) in
|
||||
## clear and encrypt only the application payload.
|
||||
let encRes = await Encrypt.request(m)
|
||||
let encrypted = encRes.valueOr:
|
||||
let encrypted = (await Encrypt.request(sdsBytes)).valueOr:
|
||||
MessageErrorEvent.emit(
|
||||
self.brokerCtx,
|
||||
MessageErrorEvent(
|
||||
requestId: channelReqId, messageHash: "", error: "encryption failed: " & error
|
||||
),
|
||||
)
|
||||
## Encryption failed *before* we could hand the segment to the
|
||||
self.markSegmentFailed(channelReqId)
|
||||
continue
|
||||
let wireBytes = seq[byte](encrypted)
|
||||
|
||||
## The `meta` field carries the Reliable Channel wire-format spec
|
||||
## marker so the ingress side of any peer can route this WakuMessage
|
||||
## to its Reliable Channel layer.
|
||||
let envelope = MessageEnvelope(
|
||||
contentTopic: self.contentTopic,
|
||||
payload: wireBytes,
|
||||
ephemeral: isEphemeral,
|
||||
payload: seq[byte](encrypted),
|
||||
ephemeral: ephemeral,
|
||||
meta: LipWireReliableChannelVersion.toBytes(),
|
||||
)
|
||||
|
||||
let sendRes = await MessagingSend.request(self.brokerCtx, envelope)
|
||||
|
||||
let messagingReqId = sendRes.valueOr:
|
||||
let messagingReqId = (await MessagingSend.request(self.brokerCtx, envelope)).valueOr:
|
||||
MessageErrorEvent.emit(
|
||||
self.brokerCtx,
|
||||
MessageErrorEvent(
|
||||
@ -279,45 +244,6 @@ proc onReadyToSend(
|
||||
|
||||
self.markSegmentInflight(channelReqId, messagingReqId)
|
||||
|
||||
proc send*(
|
||||
self: ReliableChannel, payload: seq[byte], ephemeral: bool = false
|
||||
): Future[Result[RequestId, string]] {.async: (raises: []).} =
|
||||
## Single application-level send. The first three stages of the
|
||||
## outgoing pipeline are chained explicitly so the flow is visible
|
||||
## at a glance:
|
||||
##
|
||||
## segmentation -> sds -> rate_limit_manager
|
||||
##
|
||||
## `rate_limit_manager.enqueueToSend` emits a `ReadyToSendEvent` with
|
||||
## the SDS messages cleared for transmission; the channel's listener
|
||||
## then runs the final stage (encryption -> dispatch).
|
||||
##
|
||||
## The returned `RequestId` is the channel-level parent of one-or-more
|
||||
## messaging-layer `RequestId`s; the mapping is held in
|
||||
## `self.channelReqs` until every segment is final.
|
||||
if payload.len == 0:
|
||||
return err("empty payload")
|
||||
|
||||
let channelReqId = RequestId.new(self.rng)
|
||||
let persistenceReqType =
|
||||
if ephemeral: MessagePersistence.Ephemeral else: MessagePersistence.Persistent
|
||||
|
||||
var segmentCount = 0
|
||||
var enqueued: seq[seq[byte]]
|
||||
for segmentBytes in self.segmentation.performSegmentation(payload):
|
||||
## Segments arrive already encoded; the segmentation module owns
|
||||
## the wire format so SDS only ever sees opaque bytes.
|
||||
let sdsBytes = (await self.sdsHandler.wrapOutgoing(segmentBytes)).valueOr:
|
||||
return err("SDS wrap failed: " & error)
|
||||
enqueued.add(sdsBytes)
|
||||
segmentCount.inc()
|
||||
|
||||
self.channelReqs[channelReqId] =
|
||||
ChannelReqState.init(persistenceReqType, segmentCount)
|
||||
|
||||
for sdsBytes in enqueued:
|
||||
self.rateLimit.enqueueToSend(sdsBytes)
|
||||
|
||||
return ok(channelReqId)
|
||||
|
||||
proc reportReceived(self: ReliableChannel, content: seq[byte]) =
|
||||
@ -337,8 +263,7 @@ proc reportReceived(self: ReliableChannel, content: seq[byte]) =
|
||||
)
|
||||
|
||||
proc dispatchRepair(self: ReliableChannel, wire: seq[byte]) {.async: (raises: []).} =
|
||||
## Repair rebroadcasts skip the rate-limit queue — its emissions are
|
||||
## claimed FIFO by pending sends. Pacing is done by SDS itself.
|
||||
## SDS-driven repair rebroadcast. Pacing is done by SDS itself.
|
||||
let encRes = await Encrypt.request(wire)
|
||||
let encrypted = encRes.valueOr:
|
||||
debug "SDS repair rebroadcast dropped: encryption failed",
|
||||
@ -408,15 +333,13 @@ proc new*(
|
||||
senderId: SdsParticipantID,
|
||||
segConfig: SegmentationConfig,
|
||||
sdsConfig: SdsConfig,
|
||||
rateConfig: RateLimitConfig,
|
||||
brokerCtx: BrokerContext = globalBrokerContext(),
|
||||
): T =
|
||||
## Pipeline handlers (segmentation/SDS/rate-limit) are constructed
|
||||
## inside the channel rather than handed in by the caller — they are
|
||||
## implementation details of the channel, not knobs the API consumer
|
||||
## should be wiring up. Encryption is delegated to the `Encrypt`/
|
||||
## `Decrypt` request brokers, so the channel keeps no per-instance
|
||||
## encryption state either.
|
||||
## Pipeline handlers (segmentation/SDS) are constructed inside the
|
||||
## channel rather than handed in by the caller — they are implementation
|
||||
## details of the channel, not knobs the API consumer should be wiring
|
||||
## up. Encryption is delegated to the `Encrypt`/`Decrypt` request
|
||||
## brokers, so the channel keeps no per-instance encryption state either.
|
||||
let chn = T(
|
||||
channelId: channelId,
|
||||
contentTopic: contentTopic,
|
||||
@ -424,8 +347,7 @@ proc new*(
|
||||
rng: libp2p_crypto.newRng(),
|
||||
segmentation: SegmentationHandler.new(segConfig),
|
||||
sdsHandler: SdsHandler.new(sdsConfig, channelId, senderId),
|
||||
rateLimit: RateLimitManager.new(rateConfig, channelId, brokerCtx),
|
||||
channelReqs: initOrderedTable[RequestId, ChannelReqState](),
|
||||
channelReqs: initTable[RequestId, ChannelReqState](),
|
||||
brokerCtx: brokerCtx,
|
||||
)
|
||||
|
||||
@ -434,20 +356,11 @@ proc new*(
|
||||
asyncSpawn chn.dispatchRepair(wire)
|
||||
chn.sdsHandler.start()
|
||||
|
||||
## Each channel owns its own egress + ingress + send-completion
|
||||
## listeners on `chn.brokerCtx`, filtered to traffic addressed to
|
||||
## this channel. Keeping the listeners (and the handler procs they
|
||||
## call) inside the channel lets `onReadyToSend` /
|
||||
## `onMessageReceived` / `onMessageFinal` stay private — the
|
||||
## manager doesn't need to know about them.
|
||||
discard ReadyToSendEvent.listen(
|
||||
chn.brokerCtx,
|
||||
proc(evt: ReadyToSendEvent): Future[void] {.async: (raises: []).} =
|
||||
if evt.channelId == chn.channelId:
|
||||
await chn.onReadyToSend(evt)
|
||||
,
|
||||
)
|
||||
|
||||
## Each channel owns its own ingress + send-completion listeners on
|
||||
## `chn.brokerCtx`, filtered to traffic addressed to this channel.
|
||||
## Keeping the listeners (and the handler procs they call) inside the
|
||||
## channel lets `onMessageReceived` / `onMessageFinal` stay private —
|
||||
## the manager doesn't need to know about them.
|
||||
discard MessageReceivedEvent.listen(
|
||||
chn.brokerCtx,
|
||||
proc(evt: MessageReceivedEvent): Future[void] {.async: (raises: []).} =
|
||||
|
||||
@ -18,6 +18,7 @@ import logos_delivery/api/reliable_channel_manager_api
|
||||
import logos_delivery/api/conf/channels_conf
|
||||
|
||||
import ./reliable_channel
|
||||
import ./encryption/noop_encryption
|
||||
|
||||
export reliable_channel, channels_conf
|
||||
|
||||
@ -31,6 +32,11 @@ proc new*(
|
||||
conf: ReliableChannelManagerConf,
|
||||
brokerCtx: BrokerContext = globalBrokerContext(),
|
||||
): Result[T, string] =
|
||||
if conf.rateLimitEnabled.isSome() or conf.rateLimitEpochPeriodSec.isSome() or
|
||||
conf.rateLimitMessagesPerEpoch.isSome():
|
||||
warn "channel-level rate-limit config is deprecated and ignored; " &
|
||||
"rate limiting moved to the messaging client (MessagingClientConf.rateLimit)"
|
||||
|
||||
return ok(
|
||||
T(
|
||||
channels: initTable[ChannelId, ReliableChannel](),
|
||||
@ -40,10 +46,15 @@ proc new*(
|
||||
)
|
||||
|
||||
proc start*(self: ReliableChannelManager): Result[void, string] =
|
||||
## Placeholder: per-channel listeners are installed in `ReliableChannel.new`,
|
||||
## so the manager has nothing to start at this layer. Kept for symmetry
|
||||
## with the `Waku` mount/start lifecycle and as a hook for future state.
|
||||
discard
|
||||
## Per-channel listeners are installed in `ReliableChannel.new`, so the only
|
||||
## thing to wire up here is the encryption brokers. Channels encrypt on egress
|
||||
## and decrypt on ingress via the `Encrypt`/`Decrypt` request brokers; with no
|
||||
## provider registered every send and receive would fail, so `channel_send`
|
||||
## would never reach the wire and `ChannelMessageReceivedEvent` would never
|
||||
## fire. Install the pass-through noop so channels default to unencrypted
|
||||
## payloads. `setProvider` refuses to overwrite, so an application that
|
||||
## installed its own encryption before start keeps it.
|
||||
setNoopEncryption()
|
||||
ok()
|
||||
|
||||
proc stop*(self: ReliableChannelManager) {.async.} =
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, tables]
|
||||
import std/tables
|
||||
from std/times import initDuration, getTime, toUnix, nanosecond
|
||||
import results, chronos, chronicles
|
||||
import nimcrypto/keccak
|
||||
@ -37,7 +37,7 @@ type
|
||||
acknowledgementTimeoutMs*: int
|
||||
maxRetransmissions*: int
|
||||
causalHistorySize*: int
|
||||
persistence*: Option[Persistence]
|
||||
persistence*: Opt[Persistence]
|
||||
## Durability backend. `none` runs memory-only: reliability still
|
||||
## works, state does not survive a restart.
|
||||
|
||||
|
||||
@ -9,8 +9,7 @@
|
||||
##
|
||||
## See: https://lip.logos.co/messaging/raw/reliable-channel-api.html
|
||||
|
||||
import std/options
|
||||
import ./segment_message_proto
|
||||
import results, ./segment_message_proto
|
||||
import ./segmentation_persistence
|
||||
|
||||
export segment_message_proto, segmentation_persistence
|
||||
@ -54,12 +53,12 @@ proc performSegmentation*(
|
||||
|
||||
proc handleIncomingSegment*(
|
||||
self: SegmentationHandler, segmentBytes: seq[byte]
|
||||
): Option[ReassemblyResult] =
|
||||
): Opt[ReassemblyResult] =
|
||||
## Skeleton behaviour: every segment is already a complete message
|
||||
## (since `performSegmentation` always emits one), so just hand the
|
||||
## payload straight back.
|
||||
let segment = SegmentMessageProto.decode(segmentBytes)
|
||||
return some(
|
||||
return Opt.some(
|
||||
ReassemblyResult(
|
||||
payload: segment.payload, entireMessageHash: segment.entireMessageHash
|
||||
)
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import results, chronos, chronicles
|
||||
|
||||
# Each layer has a core module (type + new/start/stop) and an api/ folder whose
|
||||
@ -40,6 +39,8 @@ import logos_delivery/messaging/[messaging_client, messaging_client_lifecycle]
|
||||
export messaging_client
|
||||
import logos_delivery/messaging/api/[subscription, send]
|
||||
export subscription, send
|
||||
import logos_delivery/messaging/rest_api/handlers as messaging_rest_api
|
||||
export messaging_rest_api
|
||||
import logos_delivery/api/events/messaging_client_events
|
||||
export messaging_client_events
|
||||
import logos_delivery/api/conf/messaging_conf
|
||||
@ -107,21 +108,38 @@ proc new*(
|
||||
proc new*(
|
||||
T: type LogosDelivery, conf: WakuNodeConf, appCallbacks: AppCallbacks = nil
|
||||
): Future[Result[LogosDelivery, string]] {.async.} =
|
||||
## Builds the full stack from a kernel `WakuNodeConf`.
|
||||
return await LogosDelivery.new(
|
||||
LogosDeliveryConf(
|
||||
kernelConf: KernelConf(conf),
|
||||
messagingConf: some(MessagingClientConf()),
|
||||
channelsConf: some(ReliableChannelManagerConf()),
|
||||
),
|
||||
appCallbacks,
|
||||
## Builds the stack from a kernel `WakuNodeConf`, selecting which API layers to
|
||||
## instantiate by `conf.entryLayer`:
|
||||
## kernel -> transport only; `conf.mode` is ignored and the config is used as-is
|
||||
## messaging -> kernel + messaging client
|
||||
## channels -> kernel + messaging + reliable channels
|
||||
## For `messaging`/`channels`, `conf.mode` (Edge/Core) sets the kernel protocol
|
||||
## flags first (messaging-level concern); for `kernel` it is skipped.
|
||||
var kernelConf = conf
|
||||
if conf.entryLayer != EntryLayer.kernel:
|
||||
applyMode(kernelConf, conf.mode).isOkOr:
|
||||
return err("failed to apply mode: " & error)
|
||||
|
||||
let ldConf = LogosDeliveryConf(
|
||||
kernelConf: KernelConf(kernelConf),
|
||||
messagingConf:
|
||||
if conf.entryLayer == EntryLayer.kernel:
|
||||
Opt.none(MessagingClientConf)
|
||||
else:
|
||||
Opt.some(MessagingClientConf()),
|
||||
channelsConf:
|
||||
if conf.entryLayer == EntryLayer.channels:
|
||||
Opt.some(ReliableChannelManagerConf())
|
||||
else:
|
||||
Opt.none(ReliableChannelManagerConf),
|
||||
)
|
||||
return await LogosDelivery.new(ldConf, appCallbacks)
|
||||
|
||||
proc new*(
|
||||
T: type LogosDelivery, kernelConf: KernelConf, appCallbacks: AppCallbacks = nil
|
||||
): Future[Result[LogosDelivery, string]] {.async.} =
|
||||
## Fleet mode: mounts the kernel only from a raw `KernelConf`; no messaging client,
|
||||
## no channel manager.
|
||||
## Kernel entry layer: mounts the kernel only from a raw `KernelConf`; no
|
||||
## messaging client, no channel manager.
|
||||
return await LogosDelivery.new(LogosDeliveryConf.init(kernelConf), appCallbacks)
|
||||
|
||||
proc new*(
|
||||
@ -136,22 +154,31 @@ proc new*(
|
||||
return await LogosDelivery.new(
|
||||
LogosDeliveryConf(
|
||||
kernelConf: kernelConf,
|
||||
messagingConf: some(messagingOverrides),
|
||||
channelsConf: some(channelsOverrides),
|
||||
messagingConf: Opt.some(messagingOverrides),
|
||||
channelsConf: Opt.some(channelsOverrides),
|
||||
),
|
||||
appCallbacks,
|
||||
)
|
||||
|
||||
proc new*(
|
||||
T: type LogosDelivery,
|
||||
entryLayer: EntryLayer = EntryLayer.channels,
|
||||
mode: LogosDeliveryMode = LogosDeliveryMode.Core,
|
||||
preset: string = "",
|
||||
messagingOverrides: MessagingClientConf = MessagingClientConf(),
|
||||
channelsOverrides: ReliableChannelManagerConf = ReliableChannelManagerConf(),
|
||||
appCallbacks: AppCallbacks = nil,
|
||||
): Future[Result[LogosDelivery, string]] {.async.} =
|
||||
## Messaging entry point (app dev). Builds the full stack from preset, mode and overrides.
|
||||
let conf = LogosDeliveryConf.init(mode, preset, messagingOverrides, channelsOverrides).valueOr:
|
||||
## Messaging entry point (app dev). Builds the stack from preset, mode and
|
||||
## overrides; `entryLayer` selects messaging vs channels (use `new(kernelConf)`
|
||||
## for a kernel-only node).
|
||||
let conf = LogosDeliveryConf.init(
|
||||
entryLayer = entryLayer,
|
||||
mode = mode,
|
||||
preset = preset,
|
||||
messagingOverrides = messagingOverrides,
|
||||
channelsOverrides = channelsOverrides,
|
||||
).valueOr:
|
||||
return err("failed to synthesize configuration: " & error)
|
||||
return await LogosDelivery.new(conf, appCallbacks)
|
||||
|
||||
@ -166,6 +193,10 @@ proc start*(self: LogosDelivery): Future[Result[void, string]] {.async.} =
|
||||
if not self.messagingClient.isNil():
|
||||
self.messagingClient.start().isOkOr:
|
||||
return err("failed to start MessagingClient: " & error)
|
||||
# Mount the messaging REST endpoints onto the kernel's REST router (no-op if
|
||||
# REST is disabled). Done here rather than in MessagingClient.start so the
|
||||
# core messaging module need not depend on the REST layer above it.
|
||||
self.messagingClient.mountRestApi()
|
||||
|
||||
if not self.reliableChannelManager.isNil():
|
||||
self.reliableChannelManager.start().isOkOr:
|
||||
@ -191,15 +222,15 @@ proc isOnline*(self: LogosDelivery): Future[Result[bool, string]] {.async.} =
|
||||
return await self.waku.isOnline()
|
||||
|
||||
proc ensureMessaging*(self: LogosDelivery): Result[void, string] =
|
||||
## Fails if the node has no messaging client (a kernel-only / fleet node).
|
||||
## Fails if the node has no messaging client (a kernel-only node).
|
||||
if self.isNil() or self.messagingClient.isNil():
|
||||
return err("node has no messaging client (kernel-only/fleet node)")
|
||||
return err("node has no messaging client (kernel-only node)")
|
||||
ok()
|
||||
|
||||
proc ensureChannels*(self: LogosDelivery): Result[void, string] =
|
||||
## Fails if the node has no reliable channel manager (a kernel-only / fleet node).
|
||||
## Fails if the node has no reliable channel manager (a kernel-only node).
|
||||
if self.isNil() or self.reliableChannelManager.isNil():
|
||||
return err("node has no reliable channel manager (kernel-only/fleet node)")
|
||||
return err("node has no reliable channel manager (kernel-only node)")
|
||||
ok()
|
||||
|
||||
# Compile-time check that each concrete type satisfies its API concept.
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
## This module is in charge of taking care of the messages that this node is expecting to
|
||||
## receive and is backed by store-v3 requests to get an additional degree of certainty
|
||||
##
|
||||
|
||||
import std/[tables, sequtils, options, sets]
|
||||
import results, std/[tables, sequtils, sets]
|
||||
import chronos, chronicles, libp2p/utility
|
||||
import brokers/broker_context
|
||||
import
|
||||
@ -112,10 +111,10 @@ proc checkStore*(self: RecvService) {.async.} =
|
||||
await self.waku.storeQueryToAny(
|
||||
StoreQueryRequest(
|
||||
includeData: false,
|
||||
pubsubTopic: some(pubsubTopic),
|
||||
pubsubTopic: Opt.some(pubsubTopic),
|
||||
contentTopics: toSeq(contentTopics),
|
||||
startTime: some(self.startTimeToCheck - DelayExtra.nanos),
|
||||
endTime: some(self.endTimeToCheck + DelayExtra.nanos),
|
||||
startTime: Opt.some(self.startTimeToCheck - DelayExtra.nanos),
|
||||
endTime: Opt.some(self.endTimeToCheck + DelayExtra.nanos),
|
||||
)
|
||||
)
|
||||
).valueOr:
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import std/[options, times], chronos
|
||||
import results, std/times, chronos
|
||||
import brokers/broker_context
|
||||
import
|
||||
logos_delivery/waku/waku_core,
|
||||
@ -24,7 +23,7 @@ type DeliveryTask* = ref object
|
||||
tryCount*: int
|
||||
state*: DeliveryState
|
||||
deliveryTime*: Moment
|
||||
firstPropagatedTime*: Option[Moment]
|
||||
firstPropagatedTime*: Opt[Moment]
|
||||
## Set once on the first successful propagation; never reset on re-publish.
|
||||
## Anchors the store-validation time cap (see propagationAge).
|
||||
propagateEventEmitted*: bool
|
||||
@ -39,7 +38,7 @@ proc new*(
|
||||
let msg = envelop.toWakuMessage()
|
||||
# TODO: use sync request for such as soon as available
|
||||
let relayShardRes = (
|
||||
RequestRelayShard.request(brokerCtx, none[PubsubTopic](), envelop.contentTopic)
|
||||
RequestRelayShard.request(brokerCtx, Opt.none(PubsubTopic), envelop.contentTopic)
|
||||
).valueOr:
|
||||
error "RequestRelayShard.request failed", error = error
|
||||
return err("Failed create DeliveryTask: " & $error)
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import chronicles, chronos, results
|
||||
import std/options
|
||||
import brokers/broker_context
|
||||
import chronicles, chronos, results, brokers/broker_context
|
||||
import logos_delivery/waku/waku_core, logos_delivery/waku/waku
|
||||
import logos_delivery/waku/api/publish
|
||||
|
||||
@ -54,7 +51,7 @@ method sendImpl*(
|
||||
task.state = DeliveryState.SuccessfullyPropagated
|
||||
task.deliveryTime = Moment.now()
|
||||
if task.firstPropagatedTime.isNone():
|
||||
task.firstPropagatedTime = some(Moment.now())
|
||||
task.firstPropagatedTime = Opt.some(Moment.now())
|
||||
# TODO: with a simple retry processor it might be more accurate to say `Sent`
|
||||
else:
|
||||
# Controversial state, publish says ok but no peer. It should not happen.
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import std/options
|
||||
import chronos, chronicles
|
||||
import results, chronos, chronicles
|
||||
import brokers/broker_context
|
||||
import logos_delivery/waku/[waku_core], logos_delivery/waku/waku_lightpush/[common, rpc]
|
||||
import logos_delivery/waku/requests/health_requests
|
||||
@ -77,7 +75,7 @@ method sendImpl*(self: RelaySendProcessor, task: DeliveryTask) {.async.} =
|
||||
task.state = DeliveryState.SuccessfullyPropagated
|
||||
task.deliveryTime = Moment.now()
|
||||
if task.firstPropagatedTime.isNone():
|
||||
task.firstPropagatedTime = some(Moment.now())
|
||||
task.firstPropagatedTime = Opt.some(Moment.now())
|
||||
else:
|
||||
# It shall not happen, but still covering it
|
||||
task.state = self.fallbackStateToSet
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
## This module reinforces the publish operation with regular store-v3 requests.
|
||||
##
|
||||
|
||||
import std/[sequtils, tables, options, typetraits]
|
||||
import std/[sequtils, tables, typetraits]
|
||||
import chronos, chronicles, libp2p/utility
|
||||
import brokers/broker_context
|
||||
import
|
||||
./[send_processor, relay_processor, lightpush_processor, delivery_task],
|
||||
logos_delivery/waku/[waku_core, waku_store/common],
|
||||
logos_delivery/waku/waku,
|
||||
logos_delivery/waku/api/[store, subscriptions, publish]
|
||||
logos_delivery/waku/api/[store, subscriptions, publish],
|
||||
logos_delivery/messaging/rate_limit_manager/rate_limit_manager
|
||||
import logos_delivery/api/events/messaging_client_events
|
||||
|
||||
logScope:
|
||||
@ -47,6 +47,9 @@ type SendService* = ref object of RootObj
|
||||
|
||||
serviceLoopHandle: Future[void] ## handle that allows to stop the async task
|
||||
sendProcessor: BaseSendProcessor
|
||||
rateLimitManager: RateLimitManager
|
||||
## Meters first transmissions against the per-epoch budget; re-publishes
|
||||
## of an already-propagated message resend the same bytes and are free.
|
||||
|
||||
waku: Waku
|
||||
checkStoreForMessages: bool
|
||||
@ -78,7 +81,10 @@ proc setupSendProcessorChain(
|
||||
return ok(processors[0])
|
||||
|
||||
proc new*(
|
||||
T: typedesc[SendService], preferP2PReliability: bool, waku: Waku
|
||||
T: typedesc[SendService],
|
||||
preferP2PReliability: bool,
|
||||
waku: Waku,
|
||||
rateLimitManager: RateLimitManager,
|
||||
): Result[T, string] =
|
||||
if not waku.hasRelay() and not waku.hasLightpush():
|
||||
return err(
|
||||
@ -95,6 +101,7 @@ proc new*(
|
||||
taskCache: newSeq[DeliveryTask](),
|
||||
serviceLoopHandle: nil,
|
||||
sendProcessor: sendProcessorChain,
|
||||
rateLimitManager: rateLimitManager,
|
||||
waku: waku,
|
||||
checkStoreForMessages: checkStoreForMessages,
|
||||
lastStoreCheckTime: Moment.now(),
|
||||
@ -246,6 +253,12 @@ proc trySendMessages(self: SendService) {.async.} =
|
||||
|
||||
for task in tasksToSend:
|
||||
# Todo, check if it has any perf gain to run them concurrent...
|
||||
if task.firstPropagatedTime.isNone():
|
||||
## Never propagated: this transmission consumes a fresh epoch slot, so
|
||||
## it must be admitted. Tasks that stay over budget remain in
|
||||
## `NextRoundRetry` and are retried as the epoch rolls over.
|
||||
(await self.rateLimitManager.admit(task.msg.payload)).isOkOr:
|
||||
continue
|
||||
await self.sendProcessor.process(task)
|
||||
|
||||
proc serviceLoop(self: SendService) {.async.} =
|
||||
@ -275,6 +288,13 @@ proc send*(self: SendService, task: DeliveryTask) {.async.} =
|
||||
error "SendService.send: failed to subscribe to content topic",
|
||||
contentTopic = task.msg.contentTopic, error = error
|
||||
|
||||
(await self.rateLimitManager.admit(task.msg.payload)).isOkOr:
|
||||
info "SendService.send: over rate-limit budget, parking task",
|
||||
requestId = task.requestId, msgHash = task.msgHash.to0xHex()
|
||||
task.state = DeliveryState.NextRoundRetry
|
||||
self.addTask(task)
|
||||
return
|
||||
|
||||
await self.sendProcessor.process(task)
|
||||
reportTaskResult(self, task)
|
||||
if task.state != DeliveryState.FailedToDeliver:
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
## Messaging layer core: the `MessagingClient` type plus its construction and
|
||||
## lifecycle. The public operations (subscribe / unsubscribe / send) live in
|
||||
## `messaging/api.nim`.
|
||||
import std/options
|
||||
import results, chronos, chronicles
|
||||
import
|
||||
logos_delivery/api/conf/messaging_conf,
|
||||
logos_delivery/api/messaging_client_api,
|
||||
logos_delivery/waku/waku,
|
||||
logos_delivery/waku/factory/conf_builder/waku_conf_builder,
|
||||
logos_delivery/messaging/delivery_service/[recv_service, send_service]
|
||||
logos_delivery/messaging/delivery_service/[recv_service, send_service],
|
||||
logos_delivery/messaging/rate_limit_manager/rate_limit_manager
|
||||
|
||||
export messaging_client_api, messaging_conf
|
||||
|
||||
@ -25,7 +25,9 @@ proc new*(
|
||||
## The messaging layer chains onto Waku: it drives the underlying Waku kernel
|
||||
## for transport while exposing its own send/recv API.
|
||||
let reliability = conf.reliabilityEnabled.get(DefaultP2pReliability)
|
||||
let sendService = ?SendService.new(reliability, waku)
|
||||
let sendService = ?SendService.new(
|
||||
reliability, waku, RateLimitManager.new(conf.rateLimit.get(DefaultRateLimitConfig))
|
||||
)
|
||||
let recvService = RecvService.new(waku)
|
||||
return ok(
|
||||
T(
|
||||
|
||||
@ -0,0 +1,58 @@
|
||||
## Rate Limit Manager for the Messaging API.
|
||||
##
|
||||
## Tracks messages sent per RLN epoch and rejects admission when the
|
||||
## limit is approached, ensuring RLN compliance on enforcing relays.
|
||||
##
|
||||
## For the skeleton this is a pass-through: every call is admitted.
|
||||
## Real per-epoch budgeting will use `queue`, `currentEpochStart`,
|
||||
## `sentInCurrentEpoch`, and `resetEpoch` to park messages and admit
|
||||
## them as the epoch rolls over.
|
||||
##
|
||||
## See: https://lip.logos.co/messaging/raw/reliable-channel-api.html
|
||||
|
||||
import std/times
|
||||
import results, chronos
|
||||
|
||||
type
|
||||
RateLimitError* {.pure.} = enum
|
||||
OverBudget
|
||||
|
||||
RateLimitConfig* = object
|
||||
enabled*: bool ## spec: rate limiting opt-in; SHOULD be true when RLN active
|
||||
epochPeriodSec*: int
|
||||
messagesPerEpoch*: int
|
||||
|
||||
RateLimitManager* = ref object
|
||||
config*: RateLimitConfig
|
||||
queue*: seq[seq[byte]]
|
||||
currentEpochStart*: Time
|
||||
sentInCurrentEpoch*: int
|
||||
|
||||
const
|
||||
DefaultEpochPeriodSec* = 600
|
||||
DefaultMessagesPerEpoch* = 1
|
||||
|
||||
DefaultRateLimitConfig* = RateLimitConfig(
|
||||
epochPeriodSec: DefaultEpochPeriodSec, messagesPerEpoch: DefaultMessagesPerEpoch
|
||||
) ## Used when no rate-limit config is supplied; `enabled` defaults false.
|
||||
|
||||
proc new*(T: type RateLimitManager, config: RateLimitConfig): T =
|
||||
return
|
||||
T(config: config, queue: @[], currentEpochStart: getTime(), sentInCurrentEpoch: 0)
|
||||
|
||||
proc admit*(
|
||||
self: RateLimitManager, msg: seq[byte]
|
||||
): Future[Result[void, RateLimitError]] {.async: (raises: []).} =
|
||||
## Skeleton behaviour: admits immediately. Real per-epoch budgeting
|
||||
## will consult `config`, `sentInCurrentEpoch`, and the elapsed
|
||||
## `epochPeriodSec` window before admitting or parking `msg`.
|
||||
return ok()
|
||||
|
||||
proc dequeueReady*(self: RateLimitManager): seq[seq[byte]] =
|
||||
## Returns the set of queued messages that may be dispatched now
|
||||
## without exceeding the configured rate limit.
|
||||
discard
|
||||
|
||||
proc resetEpoch*(self: RateLimitManager) =
|
||||
self.currentEpochStart = getTime()
|
||||
self.sentInCurrentEpoch = 0
|
||||
63
logos_delivery/messaging/rest_api/client.nim
Normal file
63
logos_delivery/messaging/rest_api/client.nim
Normal file
@ -0,0 +1,63 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import chronicles, json_serialization, presto/[route, client, common]
|
||||
import
|
||||
logos_delivery/waku/rest_api/endpoint/serdes,
|
||||
logos_delivery/waku/rest_api/endpoint/rest_serdes,
|
||||
logos_delivery/api/types,
|
||||
./types
|
||||
|
||||
export types
|
||||
|
||||
logScope:
|
||||
topics = "messaging rest client"
|
||||
|
||||
proc encodeBytes*(
|
||||
value: seq[ContentTopic], contentType: string
|
||||
): RestResult[seq[byte]] =
|
||||
return encodeBytesOf(value, contentType)
|
||||
|
||||
proc encodeBytes*(
|
||||
value: MessagingPostMessageRequest, contentType: string
|
||||
): RestResult[seq[byte]] =
|
||||
return encodeBytesOf(value, contentType)
|
||||
|
||||
proc messagingPostSubscriptionsV1*(
|
||||
body: seq[ContentTopic]
|
||||
): RestResponse[string] {.
|
||||
rest, endpoint: "/messaging/v1/subscriptions", meth: HttpMethod.MethodPost
|
||||
.}
|
||||
|
||||
proc messagingDeleteSubscriptionsV1*(
|
||||
body: seq[ContentTopic]
|
||||
): RestResponse[string] {.
|
||||
rest, endpoint: "/messaging/v1/subscriptions", meth: HttpMethod.MethodDelete
|
||||
.}
|
||||
|
||||
proc messagingPostMessagesV1*(
|
||||
body: MessagingPostMessageRequest
|
||||
): RestResponse[MessagingSendResponse] {.
|
||||
rest, endpoint: "/messaging/v1/messages", meth: HttpMethod.MethodPost
|
||||
.}
|
||||
|
||||
proc messagingGetSendEventsV1*(): RestResponse[seq[SendStatus]] {.
|
||||
rest, endpoint: "/messaging/v1/events/send", meth: HttpMethod.MethodGet
|
||||
.}
|
||||
|
||||
proc messagingGetSendEventsByIdV1*(
|
||||
requestId: string
|
||||
): RestResponse[SendStatus] {.
|
||||
rest, endpoint: "/messaging/v1/events/send/{requestId}", meth: HttpMethod.MethodGet
|
||||
.}
|
||||
|
||||
# Raw variant: the typed client above cannot decode a non-2xx text error body,
|
||||
# so status-code assertions (e.g. 404) use this string form.
|
||||
proc messagingGetSendEventsByIdRawV1*(
|
||||
requestId: string
|
||||
): RestResponse[string] {.
|
||||
rest, endpoint: "/messaging/v1/events/send/{requestId}", meth: HttpMethod.MethodGet
|
||||
.}
|
||||
|
||||
proc messagingGetReceivedMessagesV1*(): RestResponse[seq[ReceivedMessageRecord]] {.
|
||||
rest, endpoint: "/messaging/v1/events/received", meth: HttpMethod.MethodGet
|
||||
.}
|
||||
115
logos_delivery/messaging/rest_api/event_cache.nim
Normal file
115
logos_delivery/messaging/rest_api/event_cache.nim
Normal file
@ -0,0 +1,115 @@
|
||||
## In-memory cache backing the messaging event REST endpoints.
|
||||
##
|
||||
## REST is a poll-based client/server surface, so the interactive MessagingClient
|
||||
## events must be buffered here for later observation:
|
||||
## * send-related events (sent / propagated / error) grouped by request id
|
||||
## * received messages
|
||||
##
|
||||
## Both surfaces are evict-after-poll: a GET returns the buffered data and clears
|
||||
## it. A generous overflow cap bounds memory if nobody polls. All access happens
|
||||
## on the single chronos event loop (broker listeners + REST handlers), so the
|
||||
## synchronous (no-await) ops below need no locking.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[tables, deques, options]
|
||||
import results
|
||||
import logos_delivery/waku/waku_core/time
|
||||
import ./types
|
||||
|
||||
const
|
||||
DefaultMaxReceived* = 50 ## Received messages kept between polls (spec default).
|
||||
DefaultMaxSendRequests* = 10_000
|
||||
## Overflow guard: distinct request ids retained between polls.
|
||||
|
||||
type MessagingEventCache* = ref object
|
||||
# Send events grouped by request id, with FIFO insertion order for overflow
|
||||
# eviction. Cleared on poll.
|
||||
sendByReqId: Table[string, SendStatus]
|
||||
sendOrder: Deque[string]
|
||||
maxSendRequests: int
|
||||
# Received messages, bounded ring. Cleared on poll.
|
||||
received: Deque[ReceivedMessageRecord]
|
||||
maxReceived: int
|
||||
|
||||
proc new*(
|
||||
T: type MessagingEventCache,
|
||||
maxReceived = DefaultMaxReceived,
|
||||
maxSendRequests = DefaultMaxSendRequests,
|
||||
): MessagingEventCache =
|
||||
MessagingEventCache(
|
||||
sendByReqId: initTable[string, SendStatus](),
|
||||
sendOrder: initDeque[string](),
|
||||
maxSendRequests: maxSendRequests,
|
||||
received: initDeque[ReceivedMessageRecord](),
|
||||
maxReceived: maxReceived,
|
||||
)
|
||||
|
||||
proc recordSend*(
|
||||
self: MessagingEventCache,
|
||||
requestId: string,
|
||||
messageHash: string,
|
||||
kind: SendEventKind,
|
||||
error = "",
|
||||
) =
|
||||
## Append a send event to its request id's timeline, creating the entry (and
|
||||
## evicting the oldest request id past the overflow cap) as needed.
|
||||
let record = SendEventRecord(
|
||||
kind: kind,
|
||||
messageHash: messageHash,
|
||||
error: error,
|
||||
timestamp: getNowInNanosecondTime(),
|
||||
)
|
||||
|
||||
if not self.sendByReqId.hasKey(requestId):
|
||||
self.sendByReqId[requestId] = SendStatus(requestId: requestId, events: @[])
|
||||
self.sendOrder.addLast(requestId)
|
||||
|
||||
while self.sendOrder.len > self.maxSendRequests:
|
||||
let evicted = self.sendOrder.popFirst()
|
||||
self.sendByReqId.del(evicted)
|
||||
|
||||
self.sendByReqId.withValue(requestId, status):
|
||||
status[].events.add(record)
|
||||
|
||||
proc recordReceived*(
|
||||
self: MessagingEventCache, messageHash: string, message: RelayWakuMessage
|
||||
) =
|
||||
## Buffer a received message, dropping the oldest past the ring capacity.
|
||||
self.received.addLast(
|
||||
ReceivedMessageRecord(messageHash: messageHash, message: message)
|
||||
)
|
||||
|
||||
while self.received.len > self.maxReceived:
|
||||
discard self.received.popFirst()
|
||||
|
||||
proc pollAllSend*(self: MessagingEventCache): seq[SendStatus] =
|
||||
## Return all buffered send statuses and clear the store (evict-after-poll).
|
||||
for reqId in self.sendOrder:
|
||||
self.sendByReqId.withValue(reqId, status):
|
||||
result.add(status[])
|
||||
self.sendByReqId.clear()
|
||||
self.sendOrder.clear()
|
||||
|
||||
proc pollSend*(self: MessagingEventCache, requestId: string): Opt[SendStatus] =
|
||||
## Return one request id's send status and remove it (evict-after-poll).
|
||||
var status: SendStatus
|
||||
if not self.sendByReqId.pop(requestId, status):
|
||||
return Opt.none(SendStatus)
|
||||
|
||||
# Deque has no random removal; rebuild order without the polled id.
|
||||
var rebuilt = initDeque[string]()
|
||||
for reqId in self.sendOrder:
|
||||
if reqId != requestId:
|
||||
rebuilt.addLast(reqId)
|
||||
self.sendOrder = rebuilt
|
||||
|
||||
return Opt.some(status)
|
||||
|
||||
proc pollReceived*(self: MessagingEventCache): seq[ReceivedMessageRecord] =
|
||||
## Return buffered received messages (oldest first) and clear (evict-after-poll).
|
||||
for record in self.received:
|
||||
result.add(record)
|
||||
self.received.clear()
|
||||
|
||||
{.pop.}
|
||||
178
logos_delivery/messaging/rest_api/handlers.nim
Normal file
178
logos_delivery/messaging/rest_api/handlers.nim
Normal file
@ -0,0 +1,178 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos, chronicles, results, json_serialization, json_serialization/std/options
|
||||
import presto/[route, common]
|
||||
import
|
||||
logos_delivery/waku/waku,
|
||||
logos_delivery/waku/rest_api/endpoint/serdes,
|
||||
logos_delivery/waku/rest_api/endpoint/responses,
|
||||
logos_delivery/waku/rest_api/endpoint/rest_serdes,
|
||||
logos_delivery/messaging/messaging_client,
|
||||
logos_delivery/messaging/api/subscription,
|
||||
logos_delivery/messaging/api/send,
|
||||
logos_delivery/api/types,
|
||||
logos_delivery/api/events/messaging_client_events,
|
||||
./types,
|
||||
./event_cache
|
||||
|
||||
export types
|
||||
|
||||
logScope:
|
||||
topics = "messaging rest api"
|
||||
|
||||
#### Routes
|
||||
|
||||
const ROUTE_MESSAGING_SUBSCRIPTIONSV1* = "/messaging/v1/subscriptions"
|
||||
const ROUTE_MESSAGING_MESSAGESV1* = "/messaging/v1/messages"
|
||||
const ROUTE_MESSAGING_EVENTS_SENDV1* = "/messaging/v1/events/send"
|
||||
const ROUTE_MESSAGING_EVENTS_SEND_BY_IDV1* = "/messaging/v1/events/send/{requestId}"
|
||||
const ROUTE_MESSAGING_EVENTS_RECEIVEDV1* = "/messaging/v1/events/received"
|
||||
|
||||
proc installEventListeners(brokerCtx: BrokerContext, cache: MessagingEventCache) =
|
||||
## Buffers the MessagingClient events into `cache` so the poll-based REST
|
||||
## endpoints can observe them. Listeners live for the node's lifetime (the
|
||||
## captured `cache` keeps them and their data alive); no teardown is wired.
|
||||
discard MessageSentEvent.listen(
|
||||
brokerCtx,
|
||||
proc(evt: MessageSentEvent): Future[void] {.async: (raises: []).} =
|
||||
cache.recordSend($evt.requestId, evt.messageHash, SendEventKind.Sent),
|
||||
)
|
||||
|
||||
discard MessagePropagatedEvent.listen(
|
||||
brokerCtx,
|
||||
proc(evt: MessagePropagatedEvent): Future[void] {.async: (raises: []).} =
|
||||
cache.recordSend($evt.requestId, evt.messageHash, SendEventKind.Propagated),
|
||||
)
|
||||
|
||||
discard MessageErrorEvent.listen(
|
||||
brokerCtx,
|
||||
proc(evt: MessageErrorEvent): Future[void] {.async: (raises: []).} =
|
||||
cache.recordSend($evt.requestId, evt.messageHash, SendEventKind.Error, evt.error),
|
||||
)
|
||||
|
||||
discard MessageReceivedEvent.listen(
|
||||
brokerCtx,
|
||||
proc(evt: MessageReceivedEvent): Future[void] {.async: (raises: []).} =
|
||||
cache.recordReceived(evt.messageHash, toRelayWakuMessage(evt.message)),
|
||||
)
|
||||
|
||||
proc installMessagingApiHandlers*(router: var RestRouter, client: MessagingClient) =
|
||||
## Mounts the MessagingClient subscribe / unsubscribe / send operations as
|
||||
## REST endpoints onto the given (kernel-owned) router. Subscriptions are
|
||||
## keyed by content topic, matching the messaging layer's content-topic API.
|
||||
|
||||
# Event observability: buffer send/received events for the poll-based GETs.
|
||||
let eventCache = MessagingEventCache.new()
|
||||
installEventListeners(client.waku.brokerCtx, eventCache)
|
||||
|
||||
router.api(MethodOptions, ROUTE_MESSAGING_SUBSCRIPTIONSV1) do() -> RestApiResponse:
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodPost, ROUTE_MESSAGING_SUBSCRIPTIONSV1) do(
|
||||
contentBody: Option[ContentBody]
|
||||
) -> RestApiResponse:
|
||||
## Subscribes the messaging client to a list of content topics.
|
||||
let req: seq[ContentTopic] = decodeRequestBody[seq[ContentTopic]](contentBody).valueOr:
|
||||
return error
|
||||
|
||||
for contentTopic in req:
|
||||
(await client.subscribe(contentTopic)).isOkOr:
|
||||
let errorMsg = "Subscribe failed: " & error
|
||||
error "messaging SUBSCRIBE failed", error = errorMsg
|
||||
return RestApiResponse.internalServerError(errorMsg)
|
||||
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodDelete, ROUTE_MESSAGING_SUBSCRIPTIONSV1) do(
|
||||
contentBody: Option[ContentBody]
|
||||
) -> RestApiResponse:
|
||||
## Unsubscribes the messaging client from a list of content topics.
|
||||
let req: seq[ContentTopic] = decodeRequestBody[seq[ContentTopic]](contentBody).valueOr:
|
||||
return error
|
||||
|
||||
for contentTopic in req:
|
||||
client.unsubscribe(contentTopic).isOkOr:
|
||||
let errorMsg = "Unsubscribe failed: " & error
|
||||
error "messaging UNSUBSCRIBE failed", error = errorMsg
|
||||
return RestApiResponse.internalServerError(errorMsg)
|
||||
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodOptions, ROUTE_MESSAGING_MESSAGESV1) do() -> RestApiResponse:
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodPost, ROUTE_MESSAGING_MESSAGESV1) do(
|
||||
contentBody: Option[ContentBody]
|
||||
) -> RestApiResponse:
|
||||
## Sends a message through the messaging client, returning the request id.
|
||||
let req: MessagingJsonEnvelope = decodeRequestBody[MessagingJsonEnvelope](
|
||||
contentBody
|
||||
).valueOr:
|
||||
return error
|
||||
|
||||
let envelope = req.toMessageEnvelope().valueOr:
|
||||
return RestApiResponse.badRequest("Invalid message: " & error)
|
||||
|
||||
let requestId = (await client.send(envelope)).valueOr:
|
||||
error "messaging SEND failed", error = error
|
||||
return RestApiResponse.internalServerError("Send failed: " & error)
|
||||
|
||||
let data = MessagingSendResponse(requestId: $requestId)
|
||||
return RestApiResponse.jsonResponse(data, status = Http200).valueOr:
|
||||
error "An error occurred while building the json response", error = error
|
||||
return RestApiResponse.internalServerError($error)
|
||||
|
||||
#### Event observability endpoints (poll-based, evict-after-poll)
|
||||
|
||||
router.api(MethodOptions, ROUTE_MESSAGING_EVENTS_SENDV1) do() -> RestApiResponse:
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodGet, ROUTE_MESSAGING_EVENTS_SENDV1) do() -> RestApiResponse:
|
||||
## Returns all buffered send events grouped by request id, then clears them.
|
||||
let data = eventCache.pollAllSend()
|
||||
return RestApiResponse.jsonResponse(data, status = Http200).valueOr:
|
||||
error "An error occurred while building the json response", error = error
|
||||
return RestApiResponse.internalServerError($error)
|
||||
|
||||
router.api(MethodOptions, ROUTE_MESSAGING_EVENTS_SEND_BY_IDV1) do(
|
||||
requestId: string
|
||||
) -> RestApiResponse:
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodGet, ROUTE_MESSAGING_EVENTS_SEND_BY_IDV1) do(
|
||||
requestId: string
|
||||
) -> RestApiResponse:
|
||||
## Returns the buffered send events for one request id, then removes them.
|
||||
let reqId = requestId.valueOr:
|
||||
return RestApiResponse.badRequest("Invalid requestId")
|
||||
|
||||
let status = eventCache.pollSend(reqId).valueOr:
|
||||
return RestApiResponse.notFound("No send events for requestId: " & reqId)
|
||||
|
||||
return RestApiResponse.jsonResponse(status, status = Http200).valueOr:
|
||||
error "An error occurred while building the json response", error = error
|
||||
return RestApiResponse.internalServerError($error)
|
||||
|
||||
router.api(MethodOptions, ROUTE_MESSAGING_EVENTS_RECEIVEDV1) do() -> RestApiResponse:
|
||||
return RestApiResponse.ok()
|
||||
|
||||
router.api(MethodGet, ROUTE_MESSAGING_EVENTS_RECEIVEDV1) do() -> RestApiResponse:
|
||||
## Returns buffered received messages (up to the cache capacity, oldest
|
||||
## first), then clears them — optimized for polling.
|
||||
let data = eventCache.pollReceived()
|
||||
return RestApiResponse.jsonResponse(data, status = Http200).valueOr:
|
||||
error "An error occurred while building the json response", error = error
|
||||
return RestApiResponse.internalServerError($error)
|
||||
|
||||
proc mountRestApi*(client: MessagingClient) =
|
||||
## Mounts the messaging REST endpoints onto the kernel-owned REST router, if
|
||||
## the REST server is enabled. Called by the `LogosDelivery` concentrator
|
||||
## after the messaging layer has started. Lives here (not in the core
|
||||
## `messaging_client` module) so the core need not depend on the REST layer
|
||||
## above it — that would form an import cycle.
|
||||
if not client.waku.restServer.isNil():
|
||||
# The BTree route table is ref-backed, so mutating the copied router persists
|
||||
# (same pattern as the waku REST builder).
|
||||
var router = client.waku.restServer.router
|
||||
installMessagingApiHandlers(router, client)
|
||||
info "Mounted messaging REST API endpoints"
|
||||
276
logos_delivery/messaging/rest_api/types.nim
Normal file
276
logos_delivery/messaging/rest_api/types.nim
Normal file
@ -0,0 +1,276 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[sets, strformat],
|
||||
chronicles,
|
||||
results,
|
||||
json_serialization,
|
||||
json_serialization/pkg/results,
|
||||
presto/[route, client, common]
|
||||
import
|
||||
logos_delivery/waku/common/base64,
|
||||
logos_delivery/waku/rest_api/endpoint/serdes,
|
||||
logos_delivery/waku/rest_api/endpoint/relay/types as relay_types,
|
||||
logos_delivery/api/types
|
||||
|
||||
export types, relay_types
|
||||
|
||||
#### Types
|
||||
|
||||
type MessagingJsonEnvelope* = object
|
||||
## REST wire (JSON) representation of the messaging API's `MessageEnvelope`.
|
||||
## `payload` / `meta` are base64. Fields mirror `MessageEnvelope` exactly.
|
||||
payload*: Base64String
|
||||
contentTopic*: ContentTopic
|
||||
ephemeral*: Opt[bool]
|
||||
meta*: Opt[Base64String]
|
||||
|
||||
type MessagingPostMessageRequest* = MessagingJsonEnvelope
|
||||
|
||||
type MessagingSendResponse* = object
|
||||
## Returned by the send endpoint on success; correlates with
|
||||
## `MessageSentEvent` / `MessageErrorEvent`.
|
||||
requestId*: string
|
||||
|
||||
#### Type conversion
|
||||
|
||||
proc toMessageEnvelope*(msg: MessagingJsonEnvelope): Result[MessageEnvelope, string] =
|
||||
let
|
||||
payload = ?msg.payload.decode()
|
||||
meta = ?msg.meta.get(Base64String("")).decode()
|
||||
|
||||
return ok(
|
||||
MessageEnvelope(
|
||||
contentTopic: msg.contentTopic,
|
||||
payload: payload,
|
||||
ephemeral: msg.ephemeral.get(false),
|
||||
meta: meta,
|
||||
)
|
||||
)
|
||||
|
||||
#### Serialization and deserialization
|
||||
|
||||
proc writeValue*(
|
||||
writer: var JsonWriter[RestJson], value: MessagingJsonEnvelope
|
||||
) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("payload", value.payload)
|
||||
writer.writeField("contentTopic", value.contentTopic)
|
||||
if value.ephemeral.isSome():
|
||||
writer.writeField("ephemeral", value.ephemeral.get())
|
||||
if value.meta.isSome():
|
||||
writer.writeField("meta", value.meta.get())
|
||||
writer.endRecord()
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var MessagingJsonEnvelope
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
var
|
||||
payload = Opt.none(Base64String)
|
||||
contentTopic = Opt.none(ContentTopic)
|
||||
ephemeral = Opt.none(bool)
|
||||
meta = Opt.none(Base64String)
|
||||
|
||||
var keys = initHashSet[string]()
|
||||
for fieldName in readObjectFields(reader):
|
||||
# Check for repeated keys
|
||||
if keys.containsOrIncl(fieldName):
|
||||
let err =
|
||||
try:
|
||||
fmt"Multiple `{fieldName}` fields found"
|
||||
except CatchableError:
|
||||
"Multiple fields with the same name found"
|
||||
reader.raiseUnexpectedField(err, "MessagingJsonEnvelope")
|
||||
|
||||
case fieldName
|
||||
of "payload":
|
||||
payload = Opt.some(reader.readValue(Base64String))
|
||||
of "contentTopic":
|
||||
contentTopic = Opt.some(reader.readValue(ContentTopic))
|
||||
of "ephemeral":
|
||||
ephemeral = Opt.some(reader.readValue(bool))
|
||||
of "meta":
|
||||
meta = Opt.some(reader.readValue(Base64String))
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
if payload.isNone() or isEmptyOrWhitespace(string(payload.get())):
|
||||
reader.raiseUnexpectedValue("Field `payload` is missing or empty")
|
||||
|
||||
if contentTopic.isNone() or contentTopic.get().isEmptyOrWhitespace():
|
||||
reader.raiseUnexpectedValue("Field `contentTopic` is missing or empty")
|
||||
|
||||
value = MessagingJsonEnvelope(
|
||||
payload: payload.get(),
|
||||
contentTopic: contentTopic.get(),
|
||||
ephemeral: ephemeral,
|
||||
meta: meta,
|
||||
)
|
||||
|
||||
proc writeValue*(
|
||||
writer: var JsonWriter[RestJson], value: MessagingSendResponse
|
||||
) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("requestId", value.requestId)
|
||||
writer.endRecord()
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var MessagingSendResponse
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
var requestId = Opt.none(string)
|
||||
|
||||
var keys = initHashSet[string]()
|
||||
for fieldName in readObjectFields(reader):
|
||||
if keys.containsOrIncl(fieldName):
|
||||
let err =
|
||||
try:
|
||||
fmt"Multiple `{fieldName}` fields found"
|
||||
except CatchableError:
|
||||
"Multiple fields with the same name found"
|
||||
reader.raiseUnexpectedField(err, "MessagingSendResponse")
|
||||
|
||||
case fieldName
|
||||
of "requestId":
|
||||
requestId = Opt.some(reader.readValue(string))
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
if requestId.isNone():
|
||||
reader.raiseUnexpectedValue("Field `requestId` is missing")
|
||||
|
||||
value = MessagingSendResponse(requestId: requestId.get())
|
||||
|
||||
#### Event observability DTOs
|
||||
##
|
||||
## Send-related events (sent / propagated / error) are grouped per request id.
|
||||
## Received messages carry the full `WakuMessage` (serialized as
|
||||
## `RelayWakuMessage`), matching the nim `MessageReceivedEvent`. Both surfaces
|
||||
## are populated by the broker listeners installed in the messaging handlers.
|
||||
|
||||
type
|
||||
SendEventKind* {.pure.} = enum
|
||||
Sent = "sent"
|
||||
Propagated = "propagated"
|
||||
Error = "error"
|
||||
|
||||
SendEventRecord* = object
|
||||
kind*: SendEventKind
|
||||
messageHash*: string
|
||||
error*: string ## populated only for `Error`
|
||||
timestamp*: int64 ## nanoseconds, stamped when cached
|
||||
|
||||
SendStatus* = object ## All send events observed so far for a single request id.
|
||||
requestId*: string
|
||||
events*: seq[SendEventRecord]
|
||||
|
||||
ReceivedMessageRecord* = object
|
||||
messageHash*: string
|
||||
message*: RelayWakuMessage ## the received WakuMessage, full fidelity
|
||||
|
||||
#### Event DTO serialization
|
||||
|
||||
proc writeValue*(
|
||||
writer: var JsonWriter[RestJson], value: SendEventRecord
|
||||
) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("kind", $value.kind)
|
||||
writer.writeField("messageHash", value.messageHash)
|
||||
if value.error.len > 0:
|
||||
writer.writeField("error", value.error)
|
||||
writer.writeField("timestamp", value.timestamp)
|
||||
writer.endRecord()
|
||||
|
||||
proc writeValue*(
|
||||
writer: var JsonWriter[RestJson], value: SendStatus
|
||||
) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("requestId", value.requestId)
|
||||
writer.writeField("events", value.events)
|
||||
writer.endRecord()
|
||||
|
||||
proc writeValue*(
|
||||
writer: var JsonWriter[RestJson], value: ReceivedMessageRecord
|
||||
) {.raises: [IOError].} =
|
||||
writer.beginRecord()
|
||||
writer.writeField("messageHash", value.messageHash)
|
||||
writer.writeField("message", value.message)
|
||||
writer.endRecord()
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var SendEventKind
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
let s = reader.readValue(string)
|
||||
case s
|
||||
of "sent":
|
||||
value = SendEventKind.Sent
|
||||
of "propagated":
|
||||
value = SendEventKind.Propagated
|
||||
of "error":
|
||||
value = SendEventKind.Error
|
||||
else:
|
||||
reader.raiseUnexpectedValue("Invalid send event kind: " & s)
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var SendEventRecord
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
var
|
||||
kind = Opt.none(SendEventKind)
|
||||
messageHash = ""
|
||||
error = ""
|
||||
timestamp = int64(0)
|
||||
|
||||
for fieldName in readObjectFields(reader):
|
||||
case fieldName
|
||||
of "kind":
|
||||
kind = Opt.some(reader.readValue(SendEventKind))
|
||||
of "messageHash":
|
||||
messageHash = reader.readValue(string)
|
||||
of "error":
|
||||
error = reader.readValue(string)
|
||||
of "timestamp":
|
||||
timestamp = reader.readValue(int64)
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
if kind.isNone():
|
||||
reader.raiseUnexpectedValue("Field `kind` is missing")
|
||||
|
||||
value = SendEventRecord(
|
||||
kind: kind.get(), messageHash: messageHash, error: error, timestamp: timestamp
|
||||
)
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var SendStatus
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
var
|
||||
requestId = ""
|
||||
events: seq[SendEventRecord] = @[]
|
||||
|
||||
for fieldName in readObjectFields(reader):
|
||||
case fieldName
|
||||
of "requestId":
|
||||
requestId = reader.readValue(string)
|
||||
of "events":
|
||||
events = reader.readValue(seq[SendEventRecord])
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
value = SendStatus(requestId: requestId, events: events)
|
||||
|
||||
proc readValue*(
|
||||
reader: var JsonReader[RestJson], value: var ReceivedMessageRecord
|
||||
) {.raises: [SerializationError, IOError].} =
|
||||
var
|
||||
messageHash = ""
|
||||
message = RelayWakuMessage()
|
||||
|
||||
for fieldName in readObjectFields(reader):
|
||||
case fieldName
|
||||
of "messageHash":
|
||||
messageHash = reader.readValue(string)
|
||||
of "message":
|
||||
message = reader.readValue(RelayWakuMessage)
|
||||
else:
|
||||
unrecognizedFieldWarning(value)
|
||||
|
||||
value = ReceivedMessageRecord(messageHash: messageHash, message: message)
|
||||
@ -1,8 +1,6 @@
|
||||
## Waku layer API — filter (light client) operations.
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import results, chronos, chronicles
|
||||
|
||||
import logos_delivery/waku/waku
|
||||
@ -36,7 +34,7 @@ proc filterSubscribe*(
|
||||
let peer = self.node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
|
||||
return err("could not find peer with WakuFilterSubscribeCodec when subscribing")
|
||||
|
||||
let subFut = self.node.filterSubscribe(some(pubsubTopic), contentTopics, peer)
|
||||
let subFut = self.node.filterSubscribe(Opt.some(pubsubTopic), contentTopics, peer)
|
||||
if not await subFut.withTimeout(FilterOpTimeout):
|
||||
return err("filter subscription timed out")
|
||||
subFut.read().isOkOr:
|
||||
@ -57,7 +55,8 @@ proc filterUnsubscribe*(
|
||||
let peer = self.node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
|
||||
return err("could not find peer with WakuFilterSubscribeCodec when unsubscribing")
|
||||
|
||||
let unsubFut = self.node.filterUnsubscribe(some(pubsubTopic), contentTopics, peer)
|
||||
let unsubFut =
|
||||
self.node.filterUnsubscribe(Opt.some(pubsubTopic), contentTopics, peer)
|
||||
if not await unsubFut.withTimeout(FilterOpTimeout):
|
||||
return err("filter un-subscription timed out")
|
||||
unsubFut.read().isOkOr:
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
## Waku layer API — lightpush (light client publish) operations.
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import results, chronos, chronicles
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
## Waku layer API — peer management operations.
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, sequtils, strutils]
|
||||
import std/[sequtils, strutils]
|
||||
import results, chronos, chronicles
|
||||
import libp2p/[peerid, peerstore]
|
||||
|
||||
|
||||
@ -5,10 +5,8 @@
|
||||
## `WakuLightPushResult` (status code + description) that the send processors
|
||||
## branch on for their retry decisions, and expose relay/lightpush availability
|
||||
## so the messaging layer never inspects `waku.node` directly.
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import results, chronos
|
||||
|
||||
import logos_delivery/waku/waku
|
||||
@ -46,7 +44,7 @@ proc relayPushHandler*(self: Waku): PushMessageHandler =
|
||||
|
||||
proc lightpushPeerAvailable*(self: Waku, shard: PubsubTopic): bool =
|
||||
## True if a lightpush service peer is available for `shard`.
|
||||
return self.node.peerManager.selectPeer(WakuLightPushCodec, some(shard)).isSome()
|
||||
return self.node.peerManager.selectPeer(WakuLightPushCodec, Opt.some(shard)).isSome()
|
||||
|
||||
proc lightpushPublishToAny*(
|
||||
self: Waku, shard: PubsubTopic, message: WakuMessage
|
||||
@ -55,9 +53,9 @@ proc lightpushPublishToAny*(
|
||||
## through the node's lightpush flow, which attaches an RLN proof per
|
||||
## attempt when RLN is mounted. Returns SERVICE_NOT_AVAILABLE when no peer
|
||||
## is available.
|
||||
let peer = self.node.peerManager.selectPeer(WakuLightPushCodec, some(shard)).valueOr:
|
||||
let peer = self.node.peerManager.selectPeer(WakuLightPushCodec, Opt.some(shard)).valueOr:
|
||||
return lightpushResultServiceUnavailable("no lightpush peer available for shard")
|
||||
try:
|
||||
return await self.node.lightpushPublish(some(shard), message, some(peer))
|
||||
return await self.node.lightpushPublish(Opt.some(shard), message, Opt.some(peer))
|
||||
except CatchableError as e:
|
||||
return lightpushResultInternalError(e.msg)
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
## Waku layer API — store (historical query) operations.
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import results, chronos, chronicles
|
||||
|
||||
import logos_delivery/waku/waku
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import
|
||||
std/[times, strutils, os, sets, strformat, tables],
|
||||
results,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
# Simple async pool driver for postgress.
|
||||
# Inspired by: https://github.com/treeform/pg/
|
||||
{.push raises: [].}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
# The code in this file is an adaptation of the Sqlite KV Store found in nim-eth.
|
||||
# https://github.com/status-im/nim-eth/blob/master/eth/db/kvstore_sqlite3.nim
|
||||
@ -172,11 +171,11 @@ proc exec*[P](s: SqliteStmt[P, void], params: P): DatabaseResult[void] =
|
||||
res
|
||||
|
||||
template readResult(s: RawStmtPtr, column: cint, T: type): auto =
|
||||
when T is Option:
|
||||
when T is Opt:
|
||||
if sqlite3_column_type(s, column) == SQLITE_NULL:
|
||||
none(typeof(default(T).get()))
|
||||
Opt.none(typeof(default(T).get()))
|
||||
else:
|
||||
some(readSimpleResult(s, column, typeof(default(T).get())))
|
||||
Opt.some(readSimpleResult(s, column, typeof(default(T).get())))
|
||||
else:
|
||||
readSimpleResult(s, column, T)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, net],
|
||||
std/net,
|
||||
results,
|
||||
eth/keys as eth_keys,
|
||||
eth/p2p/discoveryv5/enr,
|
||||
@ -62,22 +62,22 @@ proc build*(builder: EnrBuilder): EnrResult[enr.Record] =
|
||||
## Builder extension: IP address and TCP/UDP ports
|
||||
|
||||
proc addAddressAndPorts(
|
||||
builder: var EnrBuilder, ip: IpAddress, tcpPort, udpPort: Option[Port]
|
||||
builder: var EnrBuilder, ip: IpAddress, tcpPort, udpPort: Opt[Port]
|
||||
) =
|
||||
builder.ipAddress = Opt.some(ip)
|
||||
builder.tcpPort = tcpPort.toOpt()
|
||||
builder.udpPort = udpPort.toOpt()
|
||||
builder.tcpPort = tcpPort
|
||||
builder.udpPort = udpPort
|
||||
|
||||
proc addPorts(builder: var EnrBuilder, tcp, udp: Option[Port]) =
|
||||
proc addPorts(builder: var EnrBuilder, tcp, udp: Opt[Port]) =
|
||||
# Based on: https://github.com/status-im/nim-eth/blob/4b22fcd/eth/p2p/discoveryv5/enr.nim#L166
|
||||
builder.tcpPort = tcp.toOpt()
|
||||
builder.udpPort = udp.toOpt()
|
||||
builder.tcpPort = tcp
|
||||
builder.udpPort = udp
|
||||
|
||||
proc withIpAddressAndPorts*(
|
||||
builder: var EnrBuilder,
|
||||
ipAddr = none(IpAddress),
|
||||
tcpPort = none(Port),
|
||||
udpPort = none(Port),
|
||||
ipAddr = Opt.none(IpAddress),
|
||||
tcpPort = Opt.none(Port),
|
||||
udpPort = Opt.none(Port),
|
||||
) =
|
||||
if ipAddr.isSome():
|
||||
addAddressAndPorts(builder, ipAddr.get(), tcpPort, udpPort)
|
||||
|
||||
@ -1,23 +1,9 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options, results, eth/keys as eth_keys, libp2p/crypto/crypto as libp2p_crypto
|
||||
import results, eth/keys as eth_keys, libp2p/crypto/crypto as libp2p_crypto
|
||||
|
||||
import eth/p2p/discoveryv5/enr except TypedRecord, toTypedRecord
|
||||
|
||||
## Since enr changed to result.Opt[T] from Option[T] for intercompatibility introduce a conversion between
|
||||
func toOpt*[T](o: Option[T]): Opt[T] =
|
||||
if o.isSome():
|
||||
return Opt.some(o.get())
|
||||
else:
|
||||
return Opt.none(T)
|
||||
|
||||
func toOption*[T](o: Opt[T]): Option[T] =
|
||||
if o.isSome():
|
||||
return some(o.get())
|
||||
else:
|
||||
return none(T)
|
||||
|
||||
## ENR typed record
|
||||
|
||||
# Record identity scheme
|
||||
@ -44,8 +30,8 @@ type TypedRecord* = object
|
||||
proc init(T: type TypedRecord, record: Record): T =
|
||||
TypedRecord(raw: record)
|
||||
|
||||
proc tryGet*(record: TypedRecord, field: string, T: type): Option[T] =
|
||||
return record.raw.tryGet(field, T).toOption()
|
||||
proc tryGet*(record: TypedRecord, field: string, T: type): Opt[T] =
|
||||
return record.raw.tryGet(field, T)
|
||||
|
||||
func toTyped*(record: Record): EnrResult[TypedRecord] =
|
||||
let tr = TypedRecord.init(record)
|
||||
@ -61,38 +47,38 @@ func toTyped*(record: Record): EnrResult[TypedRecord] =
|
||||
|
||||
# Typed record field accessors
|
||||
|
||||
func id*(record: TypedRecord): Option[RecordId] =
|
||||
func id*(record: TypedRecord): Opt[RecordId] =
|
||||
let fieldOpt = record.tryGet("id", string)
|
||||
if fieldOpt.isNone():
|
||||
return none(RecordId)
|
||||
return Opt.none(RecordId)
|
||||
|
||||
let field = toRecordId(fieldOpt.get()).valueOr:
|
||||
return none(RecordId)
|
||||
return Opt.none(RecordId)
|
||||
|
||||
return some(field)
|
||||
return Opt.some(field)
|
||||
|
||||
func secp256k1*(record: TypedRecord): Option[array[33, byte]] =
|
||||
func secp256k1*(record: TypedRecord): Opt[array[33, byte]] =
|
||||
record.tryGet("secp256k1", array[33, byte])
|
||||
|
||||
func ip*(record: TypedRecord): Option[array[4, byte]] =
|
||||
func ip*(record: TypedRecord): Opt[array[4, byte]] =
|
||||
record.tryGet("ip", array[4, byte])
|
||||
|
||||
func ip6*(record: TypedRecord): Option[array[16, byte]] =
|
||||
func ip6*(record: TypedRecord): Opt[array[16, byte]] =
|
||||
record.tryGet("ip6", array[16, byte])
|
||||
|
||||
func tcp*(record: TypedRecord): Option[uint16] =
|
||||
func tcp*(record: TypedRecord): Opt[uint16] =
|
||||
record.tryGet("tcp", uint16)
|
||||
|
||||
func tcp6*(record: TypedRecord): Option[uint16] =
|
||||
func tcp6*(record: TypedRecord): Opt[uint16] =
|
||||
let port = record.tryGet("tcp6", uint16)
|
||||
if port.isNone():
|
||||
return record.tcp()
|
||||
return port
|
||||
|
||||
func udp*(record: TypedRecord): Option[uint16] =
|
||||
func udp*(record: TypedRecord): Opt[uint16] =
|
||||
record.tryGet("udp", uint16)
|
||||
|
||||
func udp6*(record: TypedRecord): Option[uint16] =
|
||||
func udp6*(record: TypedRecord): Opt[uint16] =
|
||||
let port = record.tryGet("udp6", uint16)
|
||||
if port.isNone():
|
||||
return record.udp()
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import std/options
|
||||
import results
|
||||
|
||||
type PagingDirection* {.pure.} = enum
|
||||
## PagingDirection determines the direction of pagination
|
||||
@ -11,7 +11,7 @@ proc default*(): PagingDirection {.inline.} =
|
||||
proc into*(b: bool): PagingDirection =
|
||||
PagingDirection(b)
|
||||
|
||||
proc into*(b: Option[bool]): PagingDirection =
|
||||
proc into*(b: Opt[bool]): PagingDirection =
|
||||
if b.isNone():
|
||||
return default()
|
||||
b.get().into()
|
||||
@ -19,7 +19,7 @@ proc into*(b: Option[bool]): PagingDirection =
|
||||
proc into*(d: PagingDirection): bool =
|
||||
d == PagingDirection.FORWARD
|
||||
|
||||
proc into*(d: Option[PagingDirection]): bool =
|
||||
proc into*(d: Opt[PagingDirection]): bool =
|
||||
if d.isNone():
|
||||
return false
|
||||
d.get().into()
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options, libp2p/protobuf/minprotobuf, libp2p/varint
|
||||
import results, libp2p/protobuf/minprotobuf, libp2p/varint
|
||||
|
||||
export minprotobuf, varint
|
||||
|
||||
@ -39,7 +39,7 @@ proc invalidLengthField*(T: type ProtobufError, field: string): T =
|
||||
## Extension methods
|
||||
|
||||
proc write3*(proto: var ProtoBuffer, field: int, value: auto) =
|
||||
when value is Option:
|
||||
when value is Opt:
|
||||
if value.isSome():
|
||||
proto.write(field, value.get())
|
||||
else:
|
||||
|
||||
@ -6,19 +6,19 @@
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, tables], libp2p/stream/connection
|
||||
import results, std/tables, libp2p/stream/connection
|
||||
|
||||
import ./[single_token_limiter, service_metrics], ../../utils/tableutils
|
||||
|
||||
export token_bucket, setting, service_metrics
|
||||
|
||||
type PerPeerRateLimiter* = ref object of RootObj
|
||||
setting*: Option[RateLimitSetting]
|
||||
peerBucket: Table[PeerId, Option[TokenBucket]]
|
||||
setting*: Opt[RateLimitSetting]
|
||||
peerBucket: Table[PeerId, Opt[TokenBucket]]
|
||||
|
||||
proc mgetOrPut(
|
||||
perPeerRateLimiter: var PerPeerRateLimiter, peerId: PeerId
|
||||
): var Option[TokenBucket] =
|
||||
): var Opt[TokenBucket] =
|
||||
return perPeerRateLimiter.peerBucket.mgetOrPut(
|
||||
peerId, newTokenBucket(perPeerRateLimiter.setting, ReplenishMode.Continuous)
|
||||
)
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
## RequestRateLimiter
|
||||
##
|
||||
## RequestRateLimiter is a general service protection mechanism.
|
||||
@ -17,11 +16,7 @@ import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, math],
|
||||
chronicles,
|
||||
chronos/timer,
|
||||
libp2p/stream/connection,
|
||||
libp2p/utility
|
||||
results, std/math, chronicles, chronos/timer, libp2p/stream/connection, libp2p/utility
|
||||
|
||||
import std/times except TimeInterval, Duration, seconds, minutes
|
||||
|
||||
@ -41,7 +36,7 @@ const MINUTES_RATIO = 2
|
||||
|
||||
type RequestRateLimiter* = ref object of RootObj
|
||||
tokenBucket: TokenBucket
|
||||
setting*: Option[RateLimitSetting]
|
||||
setting*: Opt[RateLimitSetting]
|
||||
mainBucketSetting: RateLimitSetting
|
||||
ratio: int
|
||||
peerBucketSetting*: RateLimitSetting
|
||||
@ -80,7 +75,7 @@ proc mgetOrPut(
|
||||
requestRateLimiter: var RequestRateLimiter, peerId: PeerId, now: Moment
|
||||
): var TokenBucket =
|
||||
let bucketForNew = newTokenBucket(
|
||||
some(requestRateLimiter.peerBucketSetting), Discrete, now
|
||||
Opt.some(requestRateLimiter.peerBucketSetting), Discrete, now
|
||||
).valueOr:
|
||||
raiseAssert "This branch is not allowed to be reached as it will not be called if the setting is None."
|
||||
|
||||
@ -137,7 +132,7 @@ template checkUsageLimit*(
|
||||
bodyRejected
|
||||
|
||||
# TODO: review these ratio assumptions! Debatable!
|
||||
func calcPeriodRatio(settingOpt: Option[RateLimitSetting]): int =
|
||||
func calcPeriodRatio(settingOpt: Opt[RateLimitSetting]): int =
|
||||
settingOpt.withValue(setting):
|
||||
if setting.isUnlimited():
|
||||
return UNLIMITED_RATIO
|
||||
@ -155,7 +150,7 @@ func calcPeriodRatio(settingOpt: Option[RateLimitSetting]): int =
|
||||
|
||||
# calculates peer cache items timeout
|
||||
# effectively if a peer does not issue any requests for this amount of time will be forgotten.
|
||||
func calcCacheTimeout(settingOpt: Option[RateLimitSetting], ratio: int): Duration =
|
||||
func calcCacheTimeout(settingOpt: Opt[RateLimitSetting], ratio: int): Duration =
|
||||
settingOpt.withValue(setting):
|
||||
if setting.isUnlimited():
|
||||
return UNLIMITED_TIMEOUT
|
||||
@ -167,7 +162,7 @@ func calcCacheTimeout(settingOpt: Option[RateLimitSetting], ratio: int): Duratio
|
||||
return UNLIMITED_TIMEOUT
|
||||
|
||||
func calcPeerTokenSetting(
|
||||
setting: Option[RateLimitSetting], ratio: int
|
||||
setting: Opt[RateLimitSetting], ratio: int
|
||||
): RateLimitSetting =
|
||||
let s = setting.valueOr:
|
||||
return (0, 0.minutes)
|
||||
@ -178,7 +173,7 @@ func calcPeerTokenSetting(
|
||||
|
||||
return (peerVolume, peerPeriod)
|
||||
|
||||
proc newRequestRateLimiter*(setting: Option[RateLimitSetting]): RequestRateLimiter =
|
||||
proc newRequestRateLimiter*(setting: Opt[RateLimitSetting]): RequestRateLimiter =
|
||||
let ratio = calcPeriodRatio(setting)
|
||||
let isLimited = setting.isSome() and not setting.get().isUnlimited()
|
||||
let mainBucketSetting =
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import std/options
|
||||
import metrics, setting
|
||||
import results, metrics, setting
|
||||
|
||||
export metrics
|
||||
|
||||
@ -14,7 +13,7 @@ declarePublicCounter waku_service_requests,
|
||||
declarePublicCounter waku_service_network_bytes,
|
||||
"total incoming traffic of specific waku services", labels = ["service", "direction"]
|
||||
|
||||
proc setServiceLimitMetric*(service: string, limit: Option[RateLimitSetting]) =
|
||||
proc setServiceLimitMetric*(service: string, limit: Opt[RateLimitSetting]) =
|
||||
if limit.isSome() and not limit.get().isUnlimited():
|
||||
waku_service_requests_limit.set(
|
||||
limit.get().calculateLimitPerSecond(), labelValues = [service]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import chronos/timer, std/[tables, strutils, options], regex, results
|
||||
import chronos/timer, std/[tables, strutils], regex, results
|
||||
|
||||
# Setting for TokenBucket defined as volume over period of time
|
||||
type RateLimitSetting* = tuple[volume: int, period: Duration]
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options], chronos/timer, libp2p/stream/connection, libp2p/utility
|
||||
import results, chronos/timer, libp2p/stream/connection, libp2p/utility
|
||||
|
||||
import std/times except TimeInterval, Duration
|
||||
|
||||
@ -12,17 +12,17 @@ import ./[setting, service_metrics]
|
||||
export token_bucket, setting, service_metrics
|
||||
|
||||
proc newTokenBucket*(
|
||||
setting: Option[RateLimitSetting],
|
||||
setting: Opt[RateLimitSetting],
|
||||
replenishMode: static[ReplenishMode] = ReplenishMode.Continuous,
|
||||
startTime: Moment = Moment.now(),
|
||||
): Option[TokenBucket] =
|
||||
): Opt[TokenBucket] =
|
||||
if setting.isNone():
|
||||
return none[TokenBucket]()
|
||||
return Opt.none(TokenBucket)
|
||||
|
||||
if setting.get().isUnlimited():
|
||||
return none[TokenBucket]()
|
||||
return Opt.none(TokenBucket)
|
||||
|
||||
return some(
|
||||
return Opt.some(
|
||||
TokenBucket.new(
|
||||
capacity = setting.get().volume,
|
||||
fillDuration = setting.get().period,
|
||||
@ -40,7 +40,7 @@ proc checkUsage(
|
||||
return true
|
||||
|
||||
proc checkUsage(
|
||||
t: var Option[TokenBucket], proto: string, now = Moment.now()
|
||||
t: var Opt[TokenBucket], proto: string, now = Moment.now()
|
||||
): bool {.raises: [].} =
|
||||
if t.isNone():
|
||||
return true
|
||||
@ -49,7 +49,7 @@ proc checkUsage(
|
||||
return checkUsage(tokenBucket, proto, now)
|
||||
|
||||
template checkUsageLimit*(
|
||||
t: var Option[TokenBucket] | var TokenBucket,
|
||||
t: var Opt[TokenBucket] | var TokenBucket,
|
||||
proto: string,
|
||||
conn: Connection,
|
||||
bodyWithinLimit, bodyRejected: untyped,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[httpclient, json, uri, options], results
|
||||
import std/[httpclient, json, uri], results
|
||||
|
||||
const
|
||||
# Resource locators
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, strutils, net]
|
||||
import std/[strutils, net]
|
||||
import chronicles, eth/net/nat, results, nativesockets
|
||||
|
||||
logScope:
|
||||
@ -18,9 +18,9 @@ var singletonNat: bool = false
|
||||
# TODO: pass `NatStrategy`, not a string
|
||||
proc setupNat*(
|
||||
natConf, clientId: string, tcpPort, udpPort: Port
|
||||
): Result[
|
||||
tuple[ip: Option[IpAddress], tcpPort: Option[Port], udpPort: Option[Port]], string
|
||||
] {.gcsafe.} =
|
||||
): Result[tuple[ip: Opt[IpAddress], tcpPort: Opt[Port], udpPort: Opt[Port]], string] {.
|
||||
gcsafe
|
||||
.} =
|
||||
let strategy =
|
||||
case natConf.toLowerAscii()
|
||||
of "any": NatAny
|
||||
@ -29,8 +29,7 @@ proc setupNat*(
|
||||
of "pmp": NatPmp
|
||||
else: NatNone
|
||||
|
||||
var endpoint:
|
||||
tuple[ip: Option[IpAddress], tcpPort: Option[Port], udpPort: Option[Port]]
|
||||
var endpoint: tuple[ip: Opt[IpAddress], tcpPort: Opt[Port], udpPort: Opt[Port]]
|
||||
|
||||
if strategy != NatNone:
|
||||
## Only initialize the NAT module once
|
||||
@ -47,7 +46,7 @@ proc setupNat*(
|
||||
warn "exception in setupNat", error = getCurrentExceptionMsg()
|
||||
|
||||
if extIP.isSome():
|
||||
endpoint.ip = some(extIp.get())
|
||||
endpoint.ip = Opt.some(extIp.get())
|
||||
# RedirectPorts in considered a gcsafety violation
|
||||
# because it obtains the address of a non-gcsafe proc?
|
||||
var extPorts: Opt[(Port, Port)]
|
||||
@ -65,15 +64,15 @@ proc setupNat*(
|
||||
|
||||
if extPorts.isSome():
|
||||
let (extTcpPort, extUdpPort) = extPorts.get()
|
||||
endpoint.tcpPort = some(extTcpPort)
|
||||
endpoint.udpPort = some(extUdpPort)
|
||||
endpoint.tcpPort = Opt.some(extTcpPort)
|
||||
endpoint.udpPort = Opt.some(extUdpPort)
|
||||
else: # NatNone
|
||||
if not natConf.startsWith("extip:"):
|
||||
return err("not a valid NAT mechanism: " & $natConf)
|
||||
|
||||
try:
|
||||
# any required port redirection is assumed to be done by hand
|
||||
endpoint.ip = some(parseIpAddress(natConf[6 ..^ 1]))
|
||||
endpoint.ip = Opt.some(parseIpAddress(natConf[6 ..^ 1]))
|
||||
except ValueError:
|
||||
return err("not a valid IP address: " & $natConf[6 ..^ 1])
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import std/[strutils, math], results, regex
|
||||
|
||||
proc parseMsgSize*(input: string): Result[uint64, string] =
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
## Polyfill: `valueOr` / `withValue` templates for `std/options.Option[T]`.
|
||||
##
|
||||
## Previously provided transitively by `libp2p/utility`, removed in
|
||||
## nim-libp2p PR #2162 (commit 8a9943145). logos-delivery uses these
|
||||
## templates pervasively on `Option[T]`.
|
||||
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[macros, options]
|
||||
|
||||
template valueOr*[T](self: Option[T], body: untyped): untyped =
|
||||
let temp = (self)
|
||||
if temp.isSome:
|
||||
temp.get()
|
||||
else:
|
||||
body
|
||||
|
||||
template withValue*[T](self: Option[T], value, body: untyped): untyped =
|
||||
let temp = (self)
|
||||
if temp.isSome:
|
||||
let `value` {.inject.} = temp.get()
|
||||
body
|
||||
|
||||
macro withValue*[T](self: Option[T], value, body, elseStmt: untyped): untyped =
|
||||
let elseBody = elseStmt[0]
|
||||
quote:
|
||||
let temp = (`self`)
|
||||
if temp.isSome:
|
||||
let `value` {.inject.} = temp.get()
|
||||
`body`
|
||||
else:
|
||||
`elseBody`
|
||||
@ -1,4 +1,4 @@
|
||||
import libp2p/crypto/crypto
|
||||
import results, libp2p/crypto/crypto
|
||||
import
|
||||
chronos,
|
||||
chronicles,
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import libp2p/crypto/crypto
|
||||
import libp2p/crypto/rng
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[sequtils, strutils, options, sets, net, json],
|
||||
std/[sequtils, strutils, sets, net, json],
|
||||
results,
|
||||
chronos,
|
||||
chronicles,
|
||||
@ -40,7 +39,7 @@ type Discv5Conf* {.requiresInit.} = object
|
||||
enrAutoUpdate*: bool
|
||||
|
||||
type WakuDiscoveryV5Config* = object
|
||||
discv5Config*: Option[DiscoveryConfig]
|
||||
discv5Config*: Opt[DiscoveryConfig]
|
||||
address*: IpAddress
|
||||
port*: Port
|
||||
privateKey*: eth_keys.PrivateKey
|
||||
@ -56,21 +55,21 @@ type WakuDiscoveryV5* = ref object
|
||||
conf: WakuDiscoveryV5Config
|
||||
protocol*: protocol.Protocol
|
||||
listening*: bool
|
||||
predicate: Option[WakuDiscv5Predicate]
|
||||
peerManager: Option[PeerManager]
|
||||
predicate: Opt[WakuDiscv5Predicate]
|
||||
peerManager: Opt[PeerManager]
|
||||
topicSubscriptionQueue: AsyncEventQueue[SubscriptionEvent]
|
||||
|
||||
proc shardingPredicate*(
|
||||
record: Record, bootnodes: seq[Record] = @[]
|
||||
): Option[WakuDiscv5Predicate] =
|
||||
): Opt[WakuDiscv5Predicate] =
|
||||
## Filter peers based on relay sharding information
|
||||
let typedRecord = record.toTyped().valueOr:
|
||||
info "peer filtering failed", reason = error
|
||||
return none(WakuDiscv5Predicate)
|
||||
return Opt.none(WakuDiscv5Predicate)
|
||||
|
||||
let nodeShard = typedRecord.relaySharding().valueOr:
|
||||
info "no relay sharding information, peer filtering disabled"
|
||||
return none(WakuDiscv5Predicate)
|
||||
return Opt.none(WakuDiscv5Predicate)
|
||||
|
||||
info "peer filtering updated"
|
||||
|
||||
@ -81,14 +80,14 @@ proc shardingPredicate*(
|
||||
nodeShard.shardIds.anyIt(record.containsShard(nodeShard.clusterId, it))
|
||||
) #RFC 64 guideline
|
||||
|
||||
return some(predicate)
|
||||
return Opt.some(predicate)
|
||||
|
||||
proc new*(
|
||||
T: type WakuDiscoveryV5,
|
||||
rng: crypto.Rng,
|
||||
conf: WakuDiscoveryV5Config,
|
||||
record: Option[waku_enr.Record],
|
||||
peerManager: Option[PeerManager] = none(PeerManager),
|
||||
record: Opt[waku_enr.Record],
|
||||
peerManager: Opt[PeerManager] = Opt.none(PeerManager),
|
||||
queue: AsyncEventQueue[SubscriptionEvent] =
|
||||
newAsyncEventQueue[SubscriptionEvent](30),
|
||||
): T =
|
||||
@ -100,7 +99,7 @@ proc new*(
|
||||
privKey = conf.privateKey,
|
||||
bootstrapRecords = conf.bootstrapRecords,
|
||||
enrAutoUpdate = conf.autoupdateRecord,
|
||||
previousRecord = record.toOpt(),
|
||||
previousRecord = record,
|
||||
enrIp = Opt.none(IpAddress),
|
||||
enrTcpPort = Opt.none(Port),
|
||||
enrUdpPort = Opt.none(Port),
|
||||
@ -110,7 +109,7 @@ proc new*(
|
||||
if record.isSome():
|
||||
shardingPredicate(record.get(), conf.bootstrapRecords)
|
||||
else:
|
||||
none(WakuDiscv5Predicate)
|
||||
Opt.none(WakuDiscv5Predicate)
|
||||
|
||||
WakuDiscoveryV5(
|
||||
conf: conf,
|
||||
@ -221,7 +220,7 @@ proc logDiscv5FoundPeers(discoveredRecords: seq[waku_enr.Record]) =
|
||||
addrs = addrs, enr = recordUri, capabilities = capabilities, shards = shardsStr
|
||||
|
||||
proc findRandomPeers*(
|
||||
wd: WakuDiscoveryV5, overridePred = none(WakuDiscv5Predicate)
|
||||
wd: WakuDiscoveryV5, overridePred = Opt.none(WakuDiscv5Predicate)
|
||||
): Future[seq[waku_enr.Record]] {.async.} =
|
||||
## Find random peers to connect to using Discovery v5
|
||||
let discoveredNodes = await wd.protocol.queryRandom()
|
||||
@ -445,7 +444,7 @@ proc setupDiscoveryV5*(
|
||||
let discv5UdpPort = conf.udpPort
|
||||
|
||||
let discv5Conf = WakuDiscoveryV5Config(
|
||||
discv5Config: some(discv5Config),
|
||||
discv5Config: Opt.some(discv5Config),
|
||||
address: p2pListenAddress,
|
||||
port: discv5UdpPort,
|
||||
privateKey: eth_keys.PrivateKey(key.skkey),
|
||||
@ -455,7 +454,11 @@ proc setupDiscoveryV5*(
|
||||
|
||||
return ok(
|
||||
WakuDiscoveryV5.new(
|
||||
rng, discv5Conf, some(myENR), some(nodePeerManager), nodeTopicSubscriptionQueue
|
||||
rng,
|
||||
discv5Conf,
|
||||
Opt.some(myENR),
|
||||
Opt.some(nodePeerManager),
|
||||
nodeTopicSubscriptionQueue,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
## A set of utilities to integrate EIP-1459 DNS-based discovery
|
||||
@ -7,7 +6,7 @@ import logos_delivery/waku/compat/option_valueor
|
||||
## EIP-1459 is defined in https://eips.ethereum.org/EIPS/eip-1459
|
||||
|
||||
import
|
||||
std/[options, net, sequtils, sugar],
|
||||
std/[net, sequtils, sugar],
|
||||
chronicles,
|
||||
chronos,
|
||||
metrics,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
{.push raises: [].}
|
||||
|
||||
import std/[options, sequtils, sets]
|
||||
import std/[sequtils, sets]
|
||||
import
|
||||
chronos,
|
||||
chronicles,
|
||||
@ -51,45 +50,45 @@ type KademliaDiscoveryConf* = object
|
||||
clientMode*: bool
|
||||
xprPublishing*: bool
|
||||
|
||||
proc extractMixPubKey*(service: ServiceInfo): Option[Curve25519Key] =
|
||||
proc extractMixPubKey*(service: ServiceInfo): Opt[Curve25519Key] =
|
||||
if service.id != MixProtocolID:
|
||||
return none(Curve25519Key)
|
||||
return Opt.none(Curve25519Key)
|
||||
|
||||
if service.data.len != Curve25519KeySize:
|
||||
trace "invalid mix pub key length",
|
||||
expected = Curve25519KeySize,
|
||||
actual = service.data.len,
|
||||
dataHex = byteutils.toHex(service.data)
|
||||
return none(Curve25519Key)
|
||||
return Opt.none(Curve25519Key)
|
||||
|
||||
let key = intoCurve25519Key(service.data)
|
||||
|
||||
return some(key)
|
||||
return Opt.some(key)
|
||||
|
||||
proc remotePeerInfoFrom*(record: ExtendedPeerRecord): Option[RemotePeerInfo] =
|
||||
proc remotePeerInfoFrom*(record: ExtendedPeerRecord): Opt[RemotePeerInfo] =
|
||||
if record.addresses.len == 0:
|
||||
trace "missing addresses", peerId = record.peerId
|
||||
return none(RemotePeerInfo)
|
||||
return Opt.none(RemotePeerInfo)
|
||||
|
||||
let addrs = record.addresses.mapIt(it.address)
|
||||
if addrs.len == 0:
|
||||
trace "no dialable addresses", peerId = record.peerId
|
||||
return none(RemotePeerInfo)
|
||||
return Opt.none(RemotePeerInfo)
|
||||
|
||||
let protocols = record.services.mapIt(it.id)
|
||||
|
||||
var mixPubKey: Option[Curve25519Key] = none(Curve25519Key)
|
||||
var mixPubKey: Opt[Curve25519Key] = Opt.none(Curve25519Key)
|
||||
for service in record.services:
|
||||
let key = extractMixPubKey(service).valueOr:
|
||||
continue
|
||||
mixPubKey = some(key)
|
||||
mixPubKey = Opt.some(key)
|
||||
|
||||
trace "successfully extracted mix pub key",
|
||||
peerId = record.peerId, keyHex = byteutils.toHex(mixPubKey.get())
|
||||
|
||||
break
|
||||
|
||||
return some(
|
||||
return Opt.some(
|
||||
RemotePeerInfo.init(
|
||||
record.peerId,
|
||||
addrs = addrs,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{.push raises: [].}
|
||||
|
||||
import
|
||||
std/[options, net, math],
|
||||
std/[net, math],
|
||||
results,
|
||||
chronicles,
|
||||
libp2p/crypto/crypto,
|
||||
@ -23,14 +23,14 @@ import
|
||||
|
||||
type
|
||||
WakuNodeBuilder* = object # General
|
||||
nodeRng: Option[crypto.Rng]
|
||||
nodeKey: Option[crypto.PrivateKey]
|
||||
netConfig: Option[NetConfig]
|
||||
record: Option[enr.Record]
|
||||
nodeRng: Opt[crypto.Rng]
|
||||
nodeKey: Opt[crypto.PrivateKey]
|
||||
netConfig: Opt[NetConfig]
|
||||
record: Opt[enr.Record]
|
||||
|
||||
# Peer storage and peer manager
|
||||
peerStorage: Option[PeerStorage]
|
||||
peerStorageCapacity: Option[int]
|
||||
peerStorage: Opt[PeerStorage]
|
||||
peerStorageCapacity: Opt[int]
|
||||
|
||||
# Peer manager config
|
||||
maxRelayPeers: int
|
||||
@ -39,16 +39,16 @@ type
|
||||
shardAware: bool
|
||||
|
||||
# Libp2p switch
|
||||
switchMaxConnections: Option[int]
|
||||
switchNameResolver: Option[NameResolver]
|
||||
switchAgentString: Option[string]
|
||||
switchSslSecureKey: Option[string]
|
||||
switchSslSecureCert: Option[string]
|
||||
switchSendSignedPeerRecord: Option[bool]
|
||||
switchMaxConnections: Opt[int]
|
||||
switchNameResolver: Opt[NameResolver]
|
||||
switchAgentString: Opt[string]
|
||||
switchSslSecureKey: Opt[string]
|
||||
switchSslSecureCert: Opt[string]
|
||||
switchSendSignedPeerRecord: Opt[bool]
|
||||
circuitRelay: Relay
|
||||
|
||||
# Rate limit configs for non-relay req-resp protocols
|
||||
rateLimitSettings: Option[ProtocolRateLimitSettings]
|
||||
rateLimitSettings: Opt[ProtocolRateLimitSettings]
|
||||
|
||||
WakuNodeBuilderResult* = Result[void, string]
|
||||
|
||||
@ -60,29 +60,29 @@ proc init*(T: type WakuNodeBuilder): WakuNodeBuilder =
|
||||
## General
|
||||
|
||||
proc withRng*(builder: var WakuNodeBuilder, rng: crypto.Rng) =
|
||||
builder.nodeRng = some(rng)
|
||||
builder.nodeRng = Opt.some(rng)
|
||||
|
||||
proc withNodeKey*(builder: var WakuNodeBuilder, nodeKey: crypto.PrivateKey) =
|
||||
builder.nodeKey = some(nodeKey)
|
||||
builder.nodeKey = Opt.some(nodeKey)
|
||||
|
||||
proc withRecord*(builder: var WakuNodeBuilder, record: enr.Record) =
|
||||
builder.record = some(record)
|
||||
builder.record = Opt.some(record)
|
||||
|
||||
proc withNetworkConfiguration*(builder: var WakuNodeBuilder, config: NetConfig) =
|
||||
builder.netConfig = some(config)
|
||||
builder.netConfig = Opt.some(config)
|
||||
|
||||
proc withNetworkConfigurationDetails*(
|
||||
builder: var WakuNodeBuilder,
|
||||
bindIp: IpAddress,
|
||||
bindPort: Port,
|
||||
extIp = none(IpAddress),
|
||||
extPort = none(Port),
|
||||
extIp = Opt.none(IpAddress),
|
||||
extPort = Opt.none(Port),
|
||||
extMultiAddrs = newSeq[MultiAddress](),
|
||||
wsBindPort: Port = Port(8000),
|
||||
wsEnabled: bool = false,
|
||||
wssEnabled: bool = false,
|
||||
wakuFlags = none(CapabilitiesBitfield),
|
||||
dns4DomainName = none(string),
|
||||
wakuFlags = Opt.none(CapabilitiesBitfield),
|
||||
dns4DomainName = Opt.none(string),
|
||||
dnsNameServers = @[parseIpAddress("1.1.1.1"), parseIpAddress("1.0.0.1")],
|
||||
): WakuNodeBuilderResult {.
|
||||
deprecated: "use 'builder.withNetworkConfiguration()' instead"
|
||||
@ -93,7 +93,7 @@ proc withNetworkConfigurationDetails*(
|
||||
extIp = extIp,
|
||||
extPort = extPort,
|
||||
extMultiAddrs = extMultiAddrs,
|
||||
wsBindPort = some(wsBindPort),
|
||||
wsBindPort = Opt.some(wsBindPort),
|
||||
wsEnabled = wsEnabled,
|
||||
wssEnabled = wssEnabled,
|
||||
wakuFlags = wakuFlags,
|
||||
@ -106,10 +106,10 @@ proc withNetworkConfigurationDetails*(
|
||||
## Peer storage and peer manager
|
||||
|
||||
proc withPeerStorage*(
|
||||
builder: var WakuNodeBuilder, peerStorage: PeerStorage, capacity = none(int)
|
||||
builder: var WakuNodeBuilder, peerStorage: PeerStorage, capacity = Opt.none(int)
|
||||
) =
|
||||
if not peerStorage.isNil():
|
||||
builder.peerStorage = some(peerStorage)
|
||||
builder.peerStorage = Opt.some(peerStorage)
|
||||
|
||||
builder.peerStorageCapacity = capacity
|
||||
|
||||
@ -131,7 +131,7 @@ proc withColocationLimit*(builder: var WakuNodeBuilder, colocationLimit: int) =
|
||||
builder.colocationLimit = colocationLimit
|
||||
|
||||
proc withRateLimit*(builder: var WakuNodeBuilder, limits: ProtocolRateLimitSettings) =
|
||||
builder.rateLimitSettings = some(limits)
|
||||
builder.rateLimitSettings = Opt.some(limits)
|
||||
|
||||
proc withCircuitRelay*(builder: var WakuNodeBuilder, circuitRelay: Relay) =
|
||||
builder.circuitRelay = circuitRelay
|
||||
@ -140,21 +140,21 @@ proc withCircuitRelay*(builder: var WakuNodeBuilder, circuitRelay: Relay) =
|
||||
|
||||
proc withSwitchConfiguration*(
|
||||
builder: var WakuNodeBuilder,
|
||||
maxConnections = none(int),
|
||||
maxConnections = Opt.none(int),
|
||||
nameResolver: NameResolver = nil,
|
||||
sendSignedPeerRecord = false,
|
||||
secureKey = none(string),
|
||||
secureCert = none(string),
|
||||
agentString = none(string),
|
||||
secureKey = Opt.none(string),
|
||||
secureCert = Opt.none(string),
|
||||
agentString = Opt.none(string),
|
||||
) =
|
||||
builder.switchMaxConnections = maxConnections
|
||||
builder.switchSendSignedPeerRecord = some(sendSignedPeerRecord)
|
||||
builder.switchSendSignedPeerRecord = Opt.some(sendSignedPeerRecord)
|
||||
builder.switchSslSecureKey = secureKey
|
||||
builder.switchSslSecureCert = secureCert
|
||||
builder.switchAgentString = agentString
|
||||
|
||||
if not nameResolver.isNil():
|
||||
builder.switchNameResolver = some(nameResolver)
|
||||
builder.switchNameResolver = Opt.some(nameResolver)
|
||||
|
||||
## Build
|
||||
|
||||
@ -209,8 +209,8 @@ proc build*(builder: WakuNodeBuilder): Result[WakuNode, string] =
|
||||
let peerManager = PeerManager.new(
|
||||
switch = switch,
|
||||
storage = builder.peerStorage.get(nil),
|
||||
maxRelayPeers = some(builder.maxRelayPeers),
|
||||
maxServicePeers = some(builder.maxServicePeers),
|
||||
maxRelayPeers = Opt.some(builder.maxRelayPeers),
|
||||
maxServicePeers = Opt.some(builder.maxServicePeers),
|
||||
colocationLimit = builder.colocationLimit,
|
||||
shardedPeerManagement = builder.shardAware,
|
||||
maxConnections = builder.switchMaxConnections.get(MaxConnections),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, std/[net, options, sequtils], results
|
||||
import chronicles, std/[net, sequtils], results
|
||||
import ../waku_conf
|
||||
|
||||
logScope:
|
||||
@ -16,49 +16,49 @@ const
|
||||
## Discv5 Config Builder ##
|
||||
###########################
|
||||
type Discv5ConfBuilder* = object
|
||||
enabled*: Option[bool]
|
||||
enabled*: Opt[bool]
|
||||
|
||||
bootstrapNodes*: seq[string]
|
||||
bitsPerHop*: Option[int]
|
||||
bucketIpLimit*: Option[uint]
|
||||
enrAutoUpdate*: Option[bool]
|
||||
tableIpLimit*: Option[uint]
|
||||
udpPort*: Option[Port]
|
||||
bitsPerHop*: Opt[int]
|
||||
bucketIpLimit*: Opt[uint]
|
||||
enrAutoUpdate*: Opt[bool]
|
||||
tableIpLimit*: Opt[uint]
|
||||
udpPort*: Opt[Port]
|
||||
|
||||
proc init*(T: type Discv5ConfBuilder): Discv5ConfBuilder =
|
||||
Discv5ConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var Discv5ConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withBitsPerHop*(b: var Discv5ConfBuilder, bitsPerHop: int) =
|
||||
b.bitsPerHop = some(bitsPerHop)
|
||||
b.bitsPerHop = Opt.some(bitsPerHop)
|
||||
|
||||
proc withBucketIpLimit*(b: var Discv5ConfBuilder, bucketIpLimit: uint) =
|
||||
b.bucketIpLimit = some(bucketIpLimit)
|
||||
b.bucketIpLimit = Opt.some(bucketIpLimit)
|
||||
|
||||
proc withEnrAutoUpdate*(b: var Discv5ConfBuilder, enrAutoUpdate: bool) =
|
||||
b.enrAutoUpdate = some(enrAutoUpdate)
|
||||
b.enrAutoUpdate = Opt.some(enrAutoUpdate)
|
||||
|
||||
proc withTableIpLimit*(b: var Discv5ConfBuilder, tableIpLimit: uint) =
|
||||
b.tableIpLimit = some(tableIpLimit)
|
||||
b.tableIpLimit = Opt.some(tableIpLimit)
|
||||
|
||||
proc withUdpPort*(b: var Discv5ConfBuilder, udpPort: Port) =
|
||||
b.udpPort = some(udpPort)
|
||||
b.udpPort = Opt.some(udpPort)
|
||||
|
||||
proc withUdpPort*(b: var Discv5ConfBuilder, udpPort: uint16) =
|
||||
b.udpPort = some(Port(udpPort))
|
||||
b.udpPort = Opt.some(Port(udpPort))
|
||||
|
||||
proc withBootstrapNodes*(b: var Discv5ConfBuilder, bootstrapNodes: seq[string]) =
|
||||
# TODO: validate ENRs?
|
||||
b.bootstrapNodes = concat(b.bootstrapNodes, bootstrapNodes)
|
||||
|
||||
proc build*(b: Discv5ConfBuilder): Result[Option[Discv5Conf], string] =
|
||||
proc build*(b: Discv5ConfBuilder): Result[Opt[Discv5Conf], string] =
|
||||
if not b.enabled.get(DefaultDiscv5Enabled):
|
||||
return ok(none(Discv5Conf))
|
||||
return ok(Opt.none(Discv5Conf))
|
||||
|
||||
return ok(
|
||||
some(
|
||||
Opt.some(
|
||||
Discv5Conf(
|
||||
bootstrapNodes: b.bootstrapNodes,
|
||||
bitsPerHop: b.bitsPerHop.get(DefaultDiscv5BitsPerHop),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, std/[net, options, strutils], results
|
||||
import chronicles, std/[net, strutils], results
|
||||
import ../waku_conf
|
||||
|
||||
logScope:
|
||||
@ -8,21 +8,21 @@ logScope:
|
||||
## DNS Discovery Config Builder ##
|
||||
##################################
|
||||
type DnsDiscoveryConfBuilder* = object
|
||||
enrTreeUrl*: Option[string]
|
||||
enrTreeUrl*: Opt[string]
|
||||
nameServers*: seq[IpAddress]
|
||||
|
||||
proc init*(T: type DnsDiscoveryConfBuilder): DnsDiscoveryConfBuilder =
|
||||
DnsDiscoveryConfBuilder()
|
||||
|
||||
proc withEnrTreeUrl*(b: var DnsDiscoveryConfBuilder, enrTreeUrl: string) =
|
||||
b.enrTreeUrl = some(enrTreeUrl)
|
||||
b.enrTreeUrl = Opt.some(enrTreeUrl)
|
||||
|
||||
proc withNameServers*(b: var DnsDiscoveryConfBuilder, nameServers: seq[IpAddress]) =
|
||||
b.nameServers = nameServers
|
||||
|
||||
proc build*(b: DnsDiscoveryConfBuilder): Result[Option[DnsDiscoveryConf], string] =
|
||||
proc build*(b: DnsDiscoveryConfBuilder): Result[Opt[DnsDiscoveryConf], string] =
|
||||
if b.enrTreeUrl.isNone():
|
||||
return ok(none(DnsDiscoveryConf))
|
||||
return ok(Opt.none(DnsDiscoveryConf))
|
||||
|
||||
if isEmptyOrWhiteSpace(b.enrTreeUrl.get()):
|
||||
return err("dnsDiscovery.enrTreeUrl cannot be an empty string")
|
||||
@ -30,5 +30,7 @@ proc build*(b: DnsDiscoveryConfBuilder): Result[Option[DnsDiscoveryConf], string
|
||||
return err("dnsDiscovery.nameServers is not specified")
|
||||
|
||||
return ok(
|
||||
some(DnsDiscoveryConf(nameServers: b.nameServers, enrTreeUrl: b.enrTreeUrl.get()))
|
||||
Opt.some(
|
||||
DnsDiscoveryConf(nameServers: b.nameServers, enrTreeUrl: b.enrTreeUrl.get())
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, std/options, results
|
||||
import chronicles, results
|
||||
import ../waku_conf
|
||||
|
||||
logScope:
|
||||
@ -14,40 +14,40 @@ const
|
||||
## Filter Service Config Builder ##
|
||||
###################################
|
||||
type FilterServiceConfBuilder* = object
|
||||
enabled*: Option[bool]
|
||||
maxPeersToServe*: Option[uint32]
|
||||
subscriptionTimeout*: Option[uint16]
|
||||
maxCriteria*: Option[uint32]
|
||||
enabled*: Opt[bool]
|
||||
maxPeersToServe*: Opt[uint32]
|
||||
subscriptionTimeout*: Opt[uint16]
|
||||
maxCriteria*: Opt[uint32]
|
||||
|
||||
proc init*(T: type FilterServiceConfBuilder): FilterServiceConfBuilder =
|
||||
FilterServiceConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var FilterServiceConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withMaxPeersToServe*(b: var FilterServiceConfBuilder, maxPeersToServe: uint32) =
|
||||
b.maxPeersToServe = some(maxPeersToServe)
|
||||
b.maxPeersToServe = Opt.some(maxPeersToServe)
|
||||
|
||||
proc withMaxPeersToServeIfNotAssigned*(
|
||||
b: var FilterServiceConfBuilder, maxPeersToServe: uint32
|
||||
) =
|
||||
if b.maxPeersToServe.isNone():
|
||||
b.maxPeersToServe = some(maxPeersToServe)
|
||||
b.maxPeersToServe = Opt.some(maxPeersToServe)
|
||||
|
||||
proc withSubscriptionTimeout*(
|
||||
b: var FilterServiceConfBuilder, subscriptionTimeout: uint16
|
||||
) =
|
||||
b.subscriptionTimeout = some(subscriptionTimeout)
|
||||
b.subscriptionTimeout = Opt.some(subscriptionTimeout)
|
||||
|
||||
proc withMaxCriteria*(b: var FilterServiceConfBuilder, maxCriteria: uint32) =
|
||||
b.maxCriteria = some(maxCriteria)
|
||||
b.maxCriteria = Opt.some(maxCriteria)
|
||||
|
||||
proc build*(b: FilterServiceConfBuilder): Result[Option[FilterServiceConf], string] =
|
||||
proc build*(b: FilterServiceConfBuilder): Result[Opt[FilterServiceConf], string] =
|
||||
if not b.enabled.get(DefaultFilterEnabled):
|
||||
return ok(none(FilterServiceConf))
|
||||
return ok(Opt.none(FilterServiceConf))
|
||||
|
||||
return ok(
|
||||
some(
|
||||
Opt.some(
|
||||
FilterServiceConf(
|
||||
maxPeersToServe: b.maxPeersToServe.get(DefaultFilterMaxPeersToServe),
|
||||
subscriptionTimeout: b.subscriptionTimeout.get(DefaultFilterSubscriptionTimeout),
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import chronicles, std/options, results
|
||||
import logos_delivery/waku/discovery/waku_kademlia
|
||||
import chronicles, results, logos_delivery/waku/discovery/waku_kademlia
|
||||
import chronos
|
||||
import libp2p/[peerid, multiaddress, peerinfo]
|
||||
import libp2p/protocols/kademlia/types
|
||||
@ -15,16 +13,16 @@ const
|
||||
DefaultServiceLookupInterval* = chronos.seconds(60)
|
||||
|
||||
type KademliaDiscoveryConfBuilder* = object
|
||||
enabled*: Option[bool]
|
||||
enabled*: Opt[bool]
|
||||
bootstrapNodes*: seq[string]
|
||||
randomLookupInterval*: Option[Duration]
|
||||
serviceLookupInterval*: Option[Duration]
|
||||
randomLookupInterval*: Opt[Duration]
|
||||
serviceLookupInterval*: Opt[Duration]
|
||||
|
||||
proc init*(T: type KademliaDiscoveryConfBuilder): KademliaDiscoveryConfBuilder =
|
||||
KademliaDiscoveryConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var KademliaDiscoveryConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withBootstrapNodes*(
|
||||
b: var KademliaDiscoveryConfBuilder, bootstrapNodes: seq[string]
|
||||
@ -34,22 +32,22 @@ proc withBootstrapNodes*(
|
||||
proc withRandomLookupInterval*(
|
||||
b: var KademliaDiscoveryConfBuilder, interval: Duration
|
||||
) =
|
||||
b.randomLookupInterval = some(interval)
|
||||
b.randomLookupInterval = Opt.some(interval)
|
||||
|
||||
proc withServiceLookupInterval*(
|
||||
b: var KademliaDiscoveryConfBuilder, interval: Duration
|
||||
) =
|
||||
b.serviceLookupInterval = some(interval)
|
||||
b.serviceLookupInterval = Opt.some(interval)
|
||||
|
||||
proc build*(
|
||||
b: KademliaDiscoveryConfBuilder
|
||||
): Result[Option[KademliaDiscoveryConf], string] =
|
||||
): Result[Opt[KademliaDiscoveryConf], string] =
|
||||
# Explicit disable wins: enabled=false disables regardless of bootstrap nodes.
|
||||
if b.enabled == some(false):
|
||||
return ok(none(KademliaDiscoveryConf))
|
||||
if b.enabled == Opt.some(false):
|
||||
return ok(Opt.none(KademliaDiscoveryConf))
|
||||
# Otherwise enabled if config-enabled or any bootstrap nodes are provided.
|
||||
if not b.enabled.get(DefaultKadEnabled) and b.bootstrapNodes.len == 0:
|
||||
return ok(none(KademliaDiscoveryConf))
|
||||
return ok(Opt.none(KademliaDiscoveryConf))
|
||||
|
||||
var parsedNodes: seq[(PeerId, seq[MultiAddress])]
|
||||
for nodeStr in b.bootstrapNodes:
|
||||
@ -58,7 +56,7 @@ proc build*(
|
||||
parsedNodes.add((peerId, @[ma]))
|
||||
|
||||
return ok(
|
||||
some(
|
||||
Opt.some(
|
||||
KademliaDiscoveryConf(
|
||||
bootstrapNodes: parsedNodes,
|
||||
randomLookupInterval: b.randomLookupInterval.get(DefaultRandomLookupInterval),
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, std/[net, options], results
|
||||
import chronicles, std/net, results
|
||||
import ../waku_conf
|
||||
|
||||
logScope:
|
||||
@ -14,36 +14,36 @@ const
|
||||
## Metrics Server Config Builder ##
|
||||
###################################
|
||||
type MetricsServerConfBuilder* = object
|
||||
enabled*: Option[bool]
|
||||
enabled*: Opt[bool]
|
||||
|
||||
httpAddress*: Option[IpAddress]
|
||||
httpPort*: Option[Port]
|
||||
logging*: Option[bool]
|
||||
httpAddress*: Opt[IpAddress]
|
||||
httpPort*: Opt[Port]
|
||||
logging*: Opt[bool]
|
||||
|
||||
proc init*(T: type MetricsServerConfBuilder): MetricsServerConfBuilder =
|
||||
MetricsServerConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var MetricsServerConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withHttpAddress*(b: var MetricsServerConfBuilder, httpAddress: IpAddress) =
|
||||
b.httpAddress = some(httpAddress)
|
||||
b.httpAddress = Opt.some(httpAddress)
|
||||
|
||||
proc withHttpPort*(b: var MetricsServerConfBuilder, httpPort: Port) =
|
||||
b.httpPort = some(httpPort)
|
||||
b.httpPort = Opt.some(httpPort)
|
||||
|
||||
proc withHttpPort*(b: var MetricsServerConfBuilder, httpPort: uint16) =
|
||||
b.httpPort = some(Port(httpPort))
|
||||
b.httpPort = Opt.some(Port(httpPort))
|
||||
|
||||
proc withLogging*(b: var MetricsServerConfBuilder, logging: bool) =
|
||||
b.logging = some(logging)
|
||||
b.logging = Opt.some(logging)
|
||||
|
||||
proc build*(b: MetricsServerConfBuilder): Result[Option[MetricsServerConf], string] =
|
||||
proc build*(b: MetricsServerConfBuilder): Result[Opt[MetricsServerConf], string] =
|
||||
if not b.enabled.get(DefaultMetricsEnabled):
|
||||
return ok(none(MetricsServerConf))
|
||||
return ok(Opt.none(MetricsServerConf))
|
||||
|
||||
return ok(
|
||||
some(
|
||||
Opt.some(
|
||||
MetricsServerConf(
|
||||
httpAddress: b.httpAddress.get(DefaultMetricsHttpAddress),
|
||||
httpPort: b.httpPort.get(DefaultMetricsHttpPort),
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import chronicles, std/options, results
|
||||
import libp2p/crypto/crypto, libp2p/crypto/curve25519, libp2p_mix/curve25519
|
||||
import
|
||||
chronicles,
|
||||
results,
|
||||
libp2p/crypto/crypto,
|
||||
libp2p/crypto/curve25519,
|
||||
libp2p_mix/curve25519
|
||||
import ../waku_conf, logos_delivery/waku/waku_mix
|
||||
|
||||
logScope:
|
||||
@ -12,35 +15,39 @@ const DefaultMixEnabled: bool = false
|
||||
## Mix Config Builder ##
|
||||
##################################
|
||||
type MixConfBuilder* = object
|
||||
enabled: Option[bool]
|
||||
mixKey: Option[string]
|
||||
enabled: Opt[bool]
|
||||
mixKey: Opt[string]
|
||||
mixNodes: seq[MixNodePubInfo]
|
||||
|
||||
proc init*(T: type MixConfBuilder): MixConfBuilder =
|
||||
MixConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var MixConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withMixKey*(b: var MixConfBuilder, mixKey: string) =
|
||||
b.mixKey = some(mixKey)
|
||||
b.mixKey = Opt.some(mixKey)
|
||||
|
||||
proc withMixNodes*(b: var MixConfBuilder, mixNodes: seq[MixNodePubInfo]) =
|
||||
b.mixNodes = mixNodes
|
||||
|
||||
proc build*(b: MixConfBuilder): Result[Option[MixConf], string] =
|
||||
proc build*(b: MixConfBuilder): Result[Opt[MixConf], string] =
|
||||
if not b.enabled.get(DefaultMixEnabled):
|
||||
return ok(none[MixConf]())
|
||||
return ok(Opt.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, mixNodes: b.mixNodes))
|
||||
Opt.some(
|
||||
MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey, mixNodes: b.mixNodes)
|
||||
)
|
||||
)
|
||||
else:
|
||||
let (mixPrivKey, mixPubKey) = generateKeyPair().valueOr:
|
||||
return err("Generate key pair error: " & $error)
|
||||
return ok(
|
||||
some(MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey, mixNodes: b.mixNodes))
|
||||
Opt.some(
|
||||
MixConf(mixKey: mixPrivKey, mixPubKey: mixPubKey, mixNodes: b.mixNodes)
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import chronicles, std/[net, options], results
|
||||
import chronicles, std/net, results
|
||||
import logos_delivery/waku/factory/waku_conf
|
||||
|
||||
logScope:
|
||||
@ -11,23 +11,23 @@ const DefaultQuicPort*: Port = Port(60000)
|
||||
## QUIC Config Builder ##
|
||||
#########################
|
||||
type QuicConfBuilder* = object
|
||||
enabled*: Option[bool]
|
||||
quicPort*: Option[Port]
|
||||
enabled*: Opt[bool]
|
||||
quicPort*: Opt[Port]
|
||||
|
||||
proc init*(T: type QuicConfBuilder): QuicConfBuilder =
|
||||
QuicConfBuilder()
|
||||
|
||||
proc withEnabled*(b: var QuicConfBuilder, enabled: bool) =
|
||||
b.enabled = some(enabled)
|
||||
b.enabled = Opt.some(enabled)
|
||||
|
||||
proc withQuicPort*(b: var QuicConfBuilder, quicPort: Port) =
|
||||
b.quicPort = some(quicPort)
|
||||
b.quicPort = Opt.some(quicPort)
|
||||
|
||||
proc withQuicPort*(b: var QuicConfBuilder, quicPort: uint16) =
|
||||
b.quicPort = some(Port(quicPort))
|
||||
b.quicPort = Opt.some(Port(quicPort))
|
||||
|
||||
proc build*(b: QuicConfBuilder): Result[Option[QuicConf], string] =
|
||||
proc build*(b: QuicConfBuilder): Result[Opt[QuicConf], string] =
|
||||
if not b.enabled.get(false):
|
||||
return ok(none(QuicConf))
|
||||
return ok(Opt.none(QuicConf))
|
||||
|
||||
return ok(some(QuicConf(port: b.quicPort.get(DefaultQuicPort))))
|
||||
return ok(Opt.some(QuicConf(port: b.quicPort.get(DefaultQuicPort))))
|
||||
|
||||
@ -1,25 +1,23 @@
|
||||
import logos_delivery/waku/compat/option_valueor
|
||||
import chronicles, std/[net, options], results
|
||||
import logos_delivery/waku/common/rate_limit/setting
|
||||
import chronicles, std/net, results, logos_delivery/waku/common/rate_limit/setting
|
||||
|
||||
logScope:
|
||||
topics = "waku conf builder rate limit"
|
||||
|
||||
type RateLimitConfBuilder* = object
|
||||
strValue: Option[seq[string]]
|
||||
objValue: Option[ProtocolRateLimitSettings]
|
||||
strValue: Opt[seq[string]]
|
||||
objValue: Opt[ProtocolRateLimitSettings]
|
||||
|
||||
proc init*(T: type RateLimitConfBuilder): RateLimitConfBuilder =
|
||||
RateLimitConfBuilder()
|
||||
|
||||
proc withRateLimits*(b: var RateLimitConfBuilder, rateLimits: seq[string]) =
|
||||
b.strValue = some(rateLimits)
|
||||
b.strValue = Opt.some(rateLimits)
|
||||
|
||||
proc withRateLimitsIfNotAssigned*(
|
||||
b: var RateLimitConfBuilder, rateLimits: seq[string]
|
||||
) =
|
||||
if b.strValue.isNone() or b.strValue.get().len == 0:
|
||||
b.strValue = some(rateLimits)
|
||||
b.strValue = Opt.some(rateLimits)
|
||||
|
||||
proc build*(b: RateLimitConfBuilder): Result[ProtocolRateLimitSettings, string] =
|
||||
if b.strValue.isSome() and b.objValue.isSome():
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user