PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

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

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / agents / msg
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/agents/msg/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • msg.gnogno
  • msg_test.gnogno
msg.gnogno
1// Package msg is the wire format for messages passed between agents through a2// relay realm. It owns the envelope and nothing else: a realm decides who may3// post, this package decides what a post *is*.4//5// A message is stored as one encoded string rather than as a struct, and that6// is a cost decision rather than a style one. Storing a live object instead of7// an encoded string costs a flat ~780 B per entry in gno, in every container,8// because it is a pointer plus a struct plus a field. An envelope is read9// whole and never mutated field by field, which is exactly the case where10// encoding wins.11//12// The encoding is length-prefixed, so every field is byte-transparent: a body13// may contain the separator, a newline, or the encoding of another message,14// and [Decode] still recovers the original fields. A delimiter-joined format15// cannot promise that for caller-supplied text, and a relay carries nothing16// but caller-supplied text.17//18// A live relay built on this package is at19// [r/moul/agents/relay](/r/moul/agents/relay/v0).20package msg2122import (23	"strconv"24	"strings"25)2627// Field limits. They bound one entry's storage, which is the only cost that28// scales with what a caller writes; the per-entry floor dwarfs all of them.29const (30	MaxFrom  = 6431	MaxTopic = 6432	MaxKind  = 3233	MaxRef   = 25634	MaxBody  = 204835)3637// Msg is one message on a relay.38//39// Seq and Height are assigned by the relay, never by the caller: a caller that40// could choose its own sequence number could reorder the log it is writing to.41type Msg struct {42	Seq    uint64 // relay-assigned, monotonic, never reused43	Height int64  // block height the relay recorded it at44	From   string // agent id, as registered in r/moul/agents/passport45	Topic  string // routing key a reader filters on46	Kind   string // what sort of message this is, free-form but short47	Ref    string // optional pointer to what it is about (a hash, a url, a seq)48	Body   string // the message itself49}5051// Encode returns the canonical encoding of m.52//53// Every field is written as its decimal length, a colon, then its bytes. That54// framing is what makes the format byte-transparent, and it is the same reason55// [gno.land/p/moul/agents/commit] length-prefixes before hashing: without it,56// ("ab","c") and ("a","bc") are the same bytes.57func (m Msg) Encode() string {58	var sb strings.Builder59	writeField(&sb, strconv.FormatUint(m.Seq, 10))60	writeField(&sb, strconv.FormatInt(m.Height, 10))61	writeField(&sb, m.From)62	writeField(&sb, m.Topic)63	writeField(&sb, m.Kind)64	writeField(&sb, m.Ref)65	writeField(&sb, m.Body)66	return sb.String()67}6869// Decode parses an encoding produced by [Msg.Encode]. ok is false for anything70// else, including the empty string, which is what an unwritten ring slot holds.71func Decode(s string) (m Msg, ok bool) {72	fields := make([]string, 0, 7)73	rest := s74	for i := 0; i < 7; i++ {75		f, r, good := readField(rest)76		if !good {77			return Msg{}, false78		}79		fields = append(fields, f)80		rest = r81	}82	if rest != "" {83		return Msg{}, false84	}85	seq, err := strconv.ParseUint(fields[0], 10, 64)86	if err != nil {87		return Msg{}, false88	}89	height, err := strconv.ParseInt(fields[1], 10, 64)90	if err != nil {91		return Msg{}, false92	}93	return Msg{94		Seq:    seq,95		Height: height,96		From:   fields[2],97		Topic:  fields[3],98		Kind:   fields[4],99		Ref:    fields[5],100		Body:   fields[6],101	}, true102}103104// Validate reports the first problem with the caller-supplied fields of m, or105// the empty string when there is none. Seq and Height are not checked: they106// belong to the relay.107//108// It returns a string rather than an error so a realm can pass it straight to109// a panic without an errors import, which is the only thing realms do with it.110func (m Msg) Validate() string {111	switch {112	case m.From == "":113		return "empty from"114	case len(m.From) > MaxFrom:115		return "from too long"116	case m.Topic == "":117		return "empty topic"118	case len(m.Topic) > MaxTopic:119		return "topic too long"120	case !validTopic(m.Topic):121		return "topic must be lowercase a-z, 0-9, dash or dot"122	case len(m.Kind) > MaxKind:123		return "kind too long"124	case len(m.Ref) > MaxRef:125		return "ref too long"126	case m.Body == "":127		return "empty body"128	case len(m.Body) > MaxBody:129		return "body too long"130	}131	return ""132}133134// validTopic keeps topics usable as a filter and as a URL path element. Body,135// kind and ref are deliberately unrestricted: they are escaped at render time,136// not at write time, because a relay that silently rewrites what an agent said137// is worse than one that renders it carefully.138func validTopic(s string) bool {139	for i := 0; i < len(s); i++ {140		c := s[i]141		switch {142		case c >= 'a' && c <= 'z',143			c >= '0' && c <= '9',144			c == '-', c == '.':145		default:146			return false147		}148	}149	return true150}151152// Frame returns v as one length-prefixed field, the same framing [Msg.Encode]153// uses internally. Pair it with [Unframe] to carry a value alongside an154// encoded message without inventing a second format: a relay that records who155// signed for a message frames the address and concatenates.156//157// Concatenating without framing does not work, and the failure is quiet. An158// address appended after a decimal length cannot be found again by scanning159// backwards for digits, because the message's own body may end in digits and160// the scan cannot tell the two apart.161func Frame(v string) string {162	return strconv.Itoa(len(v)) + ":" + v163}164165// Unframe reads one framed field and returns it with the remainder.166func Unframe(s string) (field, rest string, ok bool) {167	return readField(s)168}169170func writeField(sb *strings.Builder, v string) {171	sb.WriteString(strconv.Itoa(len(v)))172	sb.WriteString(":")173	sb.WriteString(v)174}175176// readField reads one length-prefixed field and returns it with the remainder.177func readField(s string) (field, rest string, ok bool) {178	i := strings.IndexByte(s, ':')179	if i <= 0 {180		return "", "", false181	}182	n, err := strconv.Atoi(s[:i])183	if err != nil || n < 0 {184		return "", "", false185	}186	start := i + 1187	if start+n > len(s) {188		return "", "", false189	}190	return s[start : start+n], s[start+n:], true191}192

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.