Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source pure

Package msg is the wire format for messages passed between agents through a relay realm. It owns the envelope and not...

Readme View source

gno.land/p/moul/agents/msg/v0

The wire format for messages passed between agents through a relay realm.

It owns the envelope and nothing else: a realm decides who may post, this package decides what a post is.

Why an encoded string and not a struct

On chain you are charged for objects, not for data. Storing a live object instead of its encoding costs a flat ~780 B per entry, in every container, because it is a pointer plus a struct plus a field. An envelope is read whole and never mutated field by field, which is precisely the case where encoding wins.

Why length-prefixed

Every field is written as its decimal length, a colon, then its bytes, so the format is byte-transparent: a body may contain the separator, a newline, or the encoding of another message, and Decode still recovers the original fields. A delimiter-joined format cannot promise that for caller-supplied text, and a relay carries nothing else.

Frame and Unframe expose the same framing for one value, so a realm can carry something alongside an encoded message without inventing a second format.

Frame the extra value in front, never behind. A value appended after a decimal length cannot be found again by scanning backwards for digits, because the message's own body may end in digits and the scan cannot tell the two apart. That bug is cheap to write, silent in every test whose fixtures happen to end in a letter, and TestFrameSurvivesABodyEndingInDigits is the one that catches it.

The envelope

field who sets it
Seq, Height the relay. A caller that could choose its own sequence number could reorder the log it writes to
From the caller: an agent id, as registered in passport
Topic the caller: the routing key a reader filters on. Lowercase a-z0-9.-, so it is usable as a URL path element
Kind, Ref, Body the caller, unrestricted

Kind, Ref and Body are unrestricted on purpose. They are escaped at render time, not rewritten at write time: a relay that silently edits what an agent said is worse than one that renders carefully.

Live demo: r/moul/agents/relay.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

⚠️ Disclaimer: provided as-is, without warranty; not security-audited. Full disclaimer: DISCLAIMER.

Overview

Package msg is the wire format for messages passed between agents through a relay realm. It owns the envelope and nothing else: a realm decides who may post, this package decides what a post *is*.

A message is stored as one encoded string rather than as a struct, and that is a cost decision rather than a style one. Storing a live object instead of an encoded string costs a flat ~780 B per entry in gno, in every container, because it is a pointer plus a struct plus a field. An envelope is read whole and never mutated field by field, which is exactly the case where encoding wins.

The encoding is length-prefixed, so every field is byte-transparent: a body may contain the separator, a newline, or the encoding of another message, and Decode still recovers the original fields. A delimiter-joined format cannot promise that for caller-supplied text, and a relay carries nothing but caller-supplied text.

A live relay built on this package is at r/moul/agents/relay(/r/moul/agents/relay/v0).

Constants 1

const MaxFrom, MaxTopic, MaxKind, MaxRef, MaxBody

1const (
2	MaxFrom  = 64
3	MaxTopic = 64
4	MaxKind  = 32
5	MaxRef   = 256
6	MaxBody  = 2048
7)
source

Field limits. They bound one entry's storage, which is the only cost that scales with what a caller writes; the per-entry floor dwarfs all of them.

Functions 3

func Frame

1func Frame(v string) string
source

Frame returns v as one length-prefixed field, the same framing Msg.Encode uses internally. Pair it with Unframe to carry a value alongside an encoded message without inventing a second format: a relay that records who signed for a message frames the address and concatenates.

Concatenating without framing does not work, and the failure is quiet. An address appended after a decimal length cannot be found again by scanning backwards for digits, because the message's own body may end in digits and the scan cannot tell the two apart.

func Unframe

1func Unframe(s string) (field, rest string, ok bool)
source

Unframe reads one framed field and returns it with the remainder.

func Decode

1func Decode(s string) (m Msg, ok bool)
source

Decode parses an encoding produced by Msg.Encode. ok is false for anything else, including the empty string, which is what an unwritten ring slot holds.

Types 1

type Msg

struct
1type Msg struct {
2	Seq    uint64 // relay-assigned, monotonic, never reused
3	Height int64  // block height the relay recorded it at
4	From   string // agent id, as registered in r/moul/agents/passport
5	Topic  string // routing key a reader filters on
6	Kind   string // what sort of message this is, free-form but short
7	Ref    string // optional pointer to what it is about (a hash, a url, a seq)
8	Body   string // the message itself
9}
source

Msg is one message on a relay.

Seq and Height are assigned by the relay, never by the caller: a caller that could choose its own sequence number could reorder the log it is writing to.

Methods on Msg

func Encode

method on Msg
1func (m Msg) Encode() string
source

Encode returns the canonical encoding of m.

Every field is written as its decimal length, a colon, then its bytes. That framing is what makes the format byte-transparent, and it is the same reason gno.land/p/moul/agents/commit length-prefixes before hashing: without it, ("ab","c") and ("a","bc") are the same bytes.

func Validate

method on Msg
1func (m Msg) Validate() string
source

Validate reports the first problem with the caller-supplied fields of m, or the empty string when there is none. Seq and Height are not checked: they belong to the relay.

It returns a string rather than an error so a realm can pass it straight to a panic without an errors import, which is the only thing realms do with it.

Imports 2

  • strconv stdlib
  • strings stdlib

Source Files 4