feat(waku_core): add WakuEnvelope (msg + topic + hash, decode/hash-once carrier)

Introduces the immutable-by-convention ref that flows through the internal
relay dispatch so consumers reuse one decode and one hash. Includes unit tests
asserting envelope.hash == computeMessageHash and ref (no-copy) semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
NagyZoltanPeter 2026-07-15 05:44:13 +02:00
parent 3d98a28d9f
commit 35635f9843
No known key found for this signature in database
GPG Key ID: 3E1F97CF4A7B6F42
5 changed files with 86 additions and 1 deletions

View File

@ -3,6 +3,7 @@ import
./message/default_values,
./message/codec,
./message/digest,
./message/envelope,
./message/path_counters
export message, default_values, codec, digest, path_counters
export message, default_values, codec, digest, envelope, path_counters

View File

@ -0,0 +1,38 @@
## Waku message envelope.
##
## Bundles a decoded `WakuMessage` with its `pubsubTopic` and the deterministic
## `WakuMessageHash`, computed **once** at construction. The envelope is the unit
## that flows through the internal relay dispatch (relay topic handler ->
## subscription_manager -> archive / filter / store-sync / app handlers) so that
## the same message is neither re-decoded nor re-hashed by each consumer.
##
## Like `WakuMessage`, a `WakuEnvelope` is a `ref object` and **immutable by
## convention**: construct it once after validation and never mutate it. Under
## `--mm:refc` passing it around is a pointer + refcount, not a deep copy.
{.push raises: [].}
import ../topics, ./message, ./digest
type WakuEnvelope* = ref object
msg*: WakuMessage
pubsubTopic*: PubsubTopic
hash*: WakuMessageHash
proc init*(T: type WakuEnvelope, pubsubTopic: PubsubTopic, msg: WakuMessage): T =
## Builds an envelope, computing the message hash once (the single inbound-path
## hash). `msg` is referenced, not copied.
WakuEnvelope(
msg: msg, pubsubTopic: pubsubTopic, hash: computeMessageHash(pubsubTopic, msg)
)
proc shortLog*(envelope: WakuEnvelope): string =
## Compact chronicles representation: short hash + topic.
if envelope.isNil():
return "nil"
"hash=" & envelope.hash.to0xHex() & " topic=" & envelope.pubsubTopic
proc `$`*(envelope: WakuEnvelope): string =
shortLog(envelope)
{.pop.}

View File

@ -8,6 +8,7 @@ import
./waku_core/test_time,
./waku_core/test_message,
./waku_core/test_message_digest,
./waku_core/test_message_envelope,
./waku_core/test_peers,
./waku_core/test_published_address

View File

@ -2,6 +2,7 @@
import
./test_message_digest,
./test_message_envelope,
./test_namespaced_topics,
./test_peers,
./test_published_address,

View File

@ -0,0 +1,44 @@
{.used.}
import std/sequtils, stew/byteutils, testutils/unittests
import logos_delivery/waku/waku_core, ../testlib/wakucore
suite "Waku Message - Envelope":
test "envelope init computes the same hash as computeMessageHash":
## Given
let pubsubTopic = DefaultPubsubTopic
let message = fakeWakuMessage(
contentTopic = DefaultContentTopic,
payload = "\x01\x02\x03\x04TEST\x05\x06\x07\x08".toBytes(),
meta = newSeq[byte](),
ts = getNanosecondTime(1681964442),
)
## When
let envelope = WakuEnvelope.init(pubsubTopic, message)
## Then
check:
envelope.hash == computeMessageHash(pubsubTopic, message)
envelope.pubsubTopic == pubsubTopic
envelope.msg == message
test "envelope references the same message (no copy)":
let pubsubTopic = DefaultPubsubTopic
let message = fakeWakuMessage(payload = "abc".toBytes())
let envelope = WakuEnvelope.init(pubsubTopic, message)
## The envelope holds the very same ref, not a clone.
check:
envelope.msg == message
# ref identity: mutating through one is visible through the other
cast[pointer](envelope.msg) == cast[pointer](message)
test "different topics yield different hashes for the same message":
let message = fakeWakuMessage(payload = "same-payload".toBytes())
let e1 = WakuEnvelope.init("/waku/2/rs/0/0", message)
let e2 = WakuEnvelope.init("/waku/2/rs/0/1", message)
check:
e1.hash != e2.hash
e1.msg == e2.msg