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/forge/v1

Package
Open in gnoweb ↗

Overview

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

Files (18)

  • README.mdmarkdown
  • gnomod.tomltoml
  • change.gnogno
  • errors.gnogno
  • forge.gnogno
  • issue.gnogno
  • pkg.gnogno
  • ref.gnogno
  • release.gnogno
  • validate.gnogno
  • change_test.gnogno
  • forge_test.gnogno
  • helpers_test.gnogno
  • issue_test.gnogno
  • pkg_test.gnogno
  • ref_test.gnogno
  • release_test.gnogno
  • validate_test.gnogno
  • ref.gnogno
    1package forge23import (4	"crypto/sha256"5	"encoding/hex"6	"strconv"7	"strings"8

    Functions

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

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

    )
    9
    10// Log entry kinds.
    11const (
    12 KindCreate = "create" // a ref that did not exist now points somewhere
    13 KindUpdate = "update" // compare-and-swap succeeded
    14 KindForce = "force" // the tip was replaced without a matching expectation
    15 KindDelete = "delete" // the ref is gone (the log is not)
    16 KindMerge = "merge" // an update performed by merging a change request
    17)
    18
    19// Ref is the current state of one reference. The history of how it got here is
    20// in the repo log, which nothing can rewrite.
    21type Ref struct {
    22 Name string
    23 OID string
    24 UpdatedAt int64
    25 UpdatedBy address
    26}
    27
    28// LogEntry is one link of the repo's reference log. The log is append-only and
    29// hash-chained: Digest commits to every earlier entry, so publishing a single
    30// digest (in a release note, a package manifest, a tweet) pins the entire
    31// history of every ref up to that point.
    32type LogEntry struct {
    33 Seq int64
    34 Ref string
    35 OldOID string // "" when the ref did not exist
    36 NewOID string // "" on delete
    37 Actor address
    38 Height int64
    39 Kind string
    40 ChangeID int64 // the merged change, 0 otherwise
    41 Note string
    42 Digest string // hex sha256 over the previous digest and this entry
    43}
    44
    45// EntryDigest computes the chain digest of e given the previous entry's digest.
    46// It is exported so an off-chain verifier can recompute the chain byte for byte
    47// from the values it read back; the field order below is the wire format and
    48// must not change within a version.
    49func EntryDigest(prev string, e *LogEntry) string {
    50 fields := []string{
    51 prev,
    52 strconv.FormatInt(e.Seq, 10),
    53 e.Ref,
    54 e.OldOID,
    55 e.NewOID,
    56 e.Actor.String(),
    57 strconv.FormatInt(e.Height, 10),
    58 e.Kind,
    59 strconv.FormatInt(e.ChangeID, 10),
    60 e.Note,
    61 }
    62 sum := sha256.Sum256([]byte(strings.Join(fields, "\n")))
    63 return hex.EncodeToString(sum[:])
    64}
    65
    66// appendLog writes one entry and advances the chain head. Callers have already
    67// authorized and validated; this never fails.
    68func (r *Repo) appendLog(actor address, height int64, ref, oldOID, newOID, kind string, changeID int64, note string) *LogEntry {
    69 e := &LogEntry{
    70 Seq: r.nextSeq,
    71 Ref: ref,
    72 OldOID: oldOID,
    73 NewOID: newOID,
    74 Actor: actor,
    75 Height: height,
    76 Kind: kind,
    77 ChangeID: changeID,
    78 Note: note,
    79 }
    80 e.Digest = EntryDigest(r.head, e)
    81 r.log.Set(seqKey(e.Seq), e)
    82 r.head = e.Digest
    83 r.nextSeq++
    84 return e
    85}
    86
    87// SetRef moves a ref by compare-and-swap: expectedOID must be the tip the
    88// caller last saw ("" to create a ref that does not exist yet). This is git's
    89// --force-with-lease, except the lease is held by consensus rather than by the
    90// server you are pushing to, so a concurrent push cannot be silently lost and a
    91// rewritten history cannot be presented as if it had always been that way.
    92//
    93// The chain cannot check that newOID descends from expectedOID: it has no
    94// objects. That check belongs to a client holding the repo, which is exactly
    95// why every move is recorded rather than merely applied.
    96func (r *Repo) SetRef(actor address, height int64, name, expectedOID, newOID, note string) (*LogEntry, error) {
    97 if r.Archived {
    98 return nil, ErrRepoArchived
    99 }
    100 if !r.Can(actor, RoleWriter) {
    101 return nil, ErrUnauthorized
    102 }
    103 if !ValidRefName(name) {
    104 return nil, ErrInvalidRefName
    105 }
    106 if !ValidOID(newOID) {
    107 return nil, ErrInvalidOID
    108 }
    109 if !ValidLine(note, MaxNoteLen) {
    110 return nil, ErrInvalidText
    111 }
    112 cur := r.Ref(name)
    113 switch {
    114 case cur == nil && expectedOID != "":
    115 return nil, ErrRefNotFound
    116 case cur != nil && expectedOID == "":
    117 return nil, ErrRefExists
    118 case cur != nil && cur.OID != expectedOID:
    119 return nil, ErrStaleRef
    120 case cur != nil && cur.OID == newOID:
    121 return nil, ErrSameOID
    122 }
    123 kind := KindUpdate
    124 old := ""
    125 if cur == nil {
    126 kind = KindCreate
    127 } else {
    128 old = cur.OID
    129 }
    130 r.refs.Set(name, &Ref{Name: name, OID: newOID, UpdatedAt: height, UpdatedBy: actor})
    131 return r.appendLog(actor, height, name, old, newOID, kind, 0, note), nil
    132}
    133
    134// ForceSetRef moves a ref without an expectation. It needs RoleMaintainer and
    135// is permanently recorded as KindForce: the point is not to forbid a force-push
    136// (sometimes it is the right call) but to make one impossible to hide.
    137func (r *Repo) ForceSetRef(actor address, height int64, name, newOID, note string) (*LogEntry, error) {
    138 if r.Archived {
    139 return nil, ErrRepoArchived
    140 }
    141 if !r.Can(actor, RoleMaintainer) {
    142 return nil, ErrUnauthorized
    143 }
    144 if !ValidRefName(name) {
    145 return nil, ErrInvalidRefName
    146 }
    147 if !ValidOID(newOID) {
    148 return nil, ErrInvalidOID
    149 }
    150 if !ValidLine(note, MaxNoteLen) {
    151 return nil, ErrInvalidText
    152 }
    153 old := ""
    154 if cur := r.Ref(name); cur != nil {
    155 if cur.OID == newOID {
    156 return nil, ErrSameOID
    157 }
    158 old = cur.OID
    159 }
    160 r.refs.Set(name, &Ref{Name: name, OID: newOID, UpdatedAt: height, UpdatedBy: actor})
    161 return r.appendLog(actor, height, name, old, newOID, KindForce, 0, note), nil
    162}
    163
    164// DeleteRef removes a ref by compare-and-swap. The ref disappears from the
    165// current state; the log keeps every object it ever pointed at.
    166//
    167// Maintainer-only for an economic reason as much as a safety one: on gno.land
    168// the storage-deposit refund goes to whoever frees the bytes, not to whoever
    169// paid for them (`receiver := caller` in the vm keeper's deposit path, gno
    170// master 2026-09-19), so an open delete path pays for vandalism.
    171func (r *Repo) DeleteRef(actor address, height int64, name, expectedOID, note string) (*LogEntry, error) {
    172 if r.Archived {
    173 return nil, ErrRepoArchived
    174 }
    175 if !r.Can(actor, RoleMaintainer) {
    176 return nil, ErrUnauthorized
    177 }
    178 if !ValidLine(note, MaxNoteLen) {
    179 return nil, ErrInvalidText
    180 }
    181 cur := r.Ref(name)
    182 if cur == nil {
    183 return nil, ErrRefNotFound
    184 }
    185 if cur.OID != expectedOID {
    186 return nil, ErrStaleRef
    187 }
    188 if name == r.DefaultRef {
    189 return nil, ErrUnauthorized // the default branch is not deletable
    190 }
    191 r.refs.Remove(name)
    192 return r.appendLog(actor, height, name, cur.OID, "", KindDelete, 0, note), nil
    193}
    194
    195// Ref returns the current state of a ref, or nil.
    196func (r *Repo) Ref(name string) *Ref {
    197 v := r.refs.Get(name)
    198 if v == nil {
    199 return nil
    200 }
    201 return v.(*Ref)
    202}
    203
    204// IterateRefs walks refs in name order.
    205func (r *Repo) IterateRefs(cb func(*Ref) bool) {
    206 r.refs.Iterate("", "", func(_ string, value any) bool {
    207 return cb(value.(*Ref))
    208 })
    209}
    210
    211// LogHead is the digest of the last entry, "" for an empty log. Pin this value
    212// anywhere off chain and the whole history becomes falsifiable.
    213func (r *Repo) LogHead() string { return r.head }
    214
    215// LogSize is the number of entries ever appended.
    216func (r *Repo) LogSize() int { return r.log.Size() }
    217
    218// LogEntryAt returns one entry by sequence number, or nil.
    219func (r *Repo) LogEntryAt(seq int64) *LogEntry {
    220 v := r.log.Get(seqKey(seq))
    221 if v == nil {
    222 return nil
    223 }
    224 return v.(*LogEntry)
    225}
    226
    227// IterateLog walks the log oldest-first.
    228func (r *Repo) IterateLog(offset, count int, cb func(*LogEntry) bool) {
    229 if count <= 0 {
    230 count = r.log.Size()
    231 }
    232 r.log.IterateByOffset(offset, count, func(_ string, value any) bool {
    233 return cb(value.(*LogEntry))
    234 })
    235}
    236
    237// IterateLogReverse walks the log newest-first, which is what a UI wants.
    238func (r *Repo) IterateLogReverse(offset, count int, cb func(*LogEntry) bool) {
    239 if count <= 0 {
    240 count = r.log.Size()
    241 }
    242 r.log.ReverseIterateByOffset(offset, count, func(_ string, value any) bool {
    243 return cb(value.(*LogEntry))
    244 })
    245}
    246
    247// VerifyLog recomputes the whole digest chain and reports the first entry whose
    248// digest does not follow from its predecessor. It should be impossible on a
    249// live chain: it is here because a transparency log nobody can verify is just
    250// a log, and a client should be running this against the values it read back.
    251func (r *Repo) VerifyLog() (bool, int64) {
    252 prev := ""
    253 bad := int64(-1)
    254 r.log.Iterate("", "", func(_ string, value any) bool {
    255 e := value.(*LogEntry)
    256 if EntryDigest(prev, e) != e.Digest {
    257 bad = e.Seq
    258 return true
    259 }
    260 prev = e.Digest
    261 return false
    262 })
    263 return bad < 0, bad
    264}
    265