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

README.md

4.79 Kb · 110 lines

gno.land/r/moul/blog

moul's personal blog, on chain. One author, posts stored as data rather than code, and a URL that is meant to survive being pasted into someone else's chat.

gno.land/r/moul/blog             the index, newest first
gno.land/r/moul/blog:<slug>      one post
gno.land/r/moul/blog:t/<tag>     every post carrying a tag

Why private, and why no /vN

These are the same decision seen from two sides.

A post URL leaves this repository the moment it is published: it goes into chats, into other people's articles, into a link someone bookmarked. None of those can be rewritten. A /v1 would break all of them at once, which is the one failure a blog cannot absorb, so the module line carries no version.

What normally pays for a version is the ability to change the code. Here that comes from private = true instead: a private package may be re-added at the same path by the address in [addpkg].creator (gno.land/pkg/sdk/vm/keeper.go, checkRedeployPermission), and nothing else may import it. So the code can be replaced without the path moving.

The bill for that arrives as a wipe. A redeploy re-runs init() and resets every package-level variable, so every post is erased. That is survivable only because the markdown is kept off chain and tools/gnoblog -all pushes all of it back in one transaction. The chain holds the published copy; it is not the only copy.

tools/gnocontracts records the exemption from the repository's "every path ends in /vN" rule, next to r/moul/home's, with the reason.

The API

Writes are restricted to g1manfred47kzduec920z88wfr64ylksmdcedlf5.

function what it does
Set(cur, slug, title, date, tags, body) create or replace a post, the ordinary publish
Append(cur, slug, body) append, for a body too large for one transaction
Delete(cur, slug) remove a post
SetIntro(cur, body) the markdown above the index; "" restores the default
Get(slug) one post's raw markdown
Count() · Revision() how many posts, and how many writes have been accepted
Manifest() slug⇥rev⇥date⇥len⇥sha256 per post, the diff surface

A slug is 1 to 64 bytes of [a-z0-9._-]. No ':' and no '/', so a slug can never break out of its own render path. intro is refused, because intro.md names the index header on the local side and a post there would have nowhere to live.

A date is exactly YYYY-MM-DD, and that is load-bearing rather than cosmetic: posts are kept in a second avl tree keyed by "<date>/<slug>" and walked in reverse, so lexical order has to be chronological order. gno has no sort.Slice; this is what replaces it.

Manifest() is the whole sync protocol

One vm/qeval returns a line per post carrying a sha256 over the title, date, tags and body together. So gnoblog status can say exactly which local file needs a transaction without downloading a single body, and changing any one of the four fields changes the hash.

Both halves build that hashed record the same way, and neither can check the other at run time: the realm's test asserts the record, and the tool's test pins its digest as a literal. If one side ever drifts, the tool reports every post as up to date while the chain holds the old text.

Escaping

The body renders verbatim. It is markdown written by the one address that can call Set, and escaping it would turn every heading and link in a post into literal text.

Titles, tags and index excerpts go through p/moul/kit/ui anyway. Not because the author is untrusted, but because a title with a ] in it would otherwise break the link it sits inside, and that is a rendering bug with no attacker in it at all.

Publishing a post

The markdown lives outside this repository; tools/gnoblog never defaults to a path to it.

1go -C tools tool gnoblog -content DIR status   # what differs from the chain
2go -C tools tool gnoblog -content DIR tx       # the transaction that fixes it

tx writes an unsigned transaction document and the two gnokey commands that sign and broadcast it. Several posts go in one document, so publishing three is one signature and one atomic broadcast. After a redeploy, tx -all.


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

Dependency graph:

gno.land/r/moul/blog dependency graph

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