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

editions.gno

3.61 Kb · 123 lines
  1package validator_scores
  2
  3import (
  4	"strconv"
  5	"strings"
  6)
  7
  8// An edition keeps each candidate as its validated payload line, in the
  9// order added. One string per candidate costs about 160 bytes of storage
 10// deposit; a struct in an AVL tree cost about 3,700 bytes (measured on
 11// gnodev at the gnoland-1 runtime).
 12type edition struct {
 13	number          int
 14	computedAt      string
 15	scorecardSHA256 string
 16	arenas          []string
 17	sealed          bool
 18	lines           []string // "operator|moniker|total|rank|github|s1,...,sN"
 19}
 20
 21// editions holds the sealed editions in order, then at most one open
 22// edition, always last.
 23var editions []*edition
 24
 25// operatorLen is the length of a canonical g1 address, the prefix of a line.
 26const operatorLen = 40
 27
 28// find returns the line stored for operator, if any.
 29func (e *edition) find(operator string) (string, bool) {
 30	if len(operator) != operatorLen {
 31		return "", false
 32	}
 33	for _, line := range e.lines {
 34		if line[:operatorLen] == operator {
 35			return line, true
 36		}
 37	}
 38	return "", false
 39}
 40
 41func openEdition() *edition {
 42	if len(editions) == 0 || editions[len(editions)-1].sealed {
 43		return nil
 44	}
 45	return editions[len(editions)-1]
 46}
 47
 48func requireOpen(number int) *edition {
 49	e := openEdition()
 50	if e == nil || e.number != number {
 51		panic("validator_scores: edition " + strconv.Itoa(number) + " is not open")
 52	}
 53	return e
 54}
 55
 56// OpenEdition starts a new edition and returns its number. Publisher only.
 57// arenas is a comma-separated list, for example "challenge,test13,firedrill".
 58func OpenEdition(cur realm, computedAt, scorecardSHA256, arenas string) int {
 59	requirePublisher(cur)
 60	if openEdition() != nil {
 61		panic("validator_scores: an edition is already open")
 62	}
 63	if !isComputedAt(computedAt) {
 64		panic("validator_scores: invalid computedAt")
 65	}
 66	if !isSHA256Hex(scorecardSHA256) {
 67		panic("validator_scores: invalid scorecard SHA-256")
 68	}
 69	e := &edition{
 70		number:          len(editions) + 1,
 71		computedAt:      computedAt,
 72		scorecardSHA256: scorecardSHA256,
 73		arenas:          parseArenas(arenas),
 74	}
 75	editions = append(editions, e)
 76	return e.number
 77}
 78
 79// AddCandidates adds one candidate per payload line to the open edition.
 80// Publisher only. The whole payload is validated before anything is stored.
 81func AddCandidates(cur realm, number int, payload string) {
 82	requirePublisher(cur)
 83	e := requireOpen(number)
 84	if payload == "" {
 85		panic("validator_scores: empty payload")
 86	}
 87	lines := strings.Split(payload, "\n")
 88	seen := map[string]bool{}
 89	for i, line := range lines {
 90		operator := checkLine(line, len(e.arenas), i+1)
 91		if _, found := e.find(operator); found || seen[operator] {
 92			panic(lineError(i+1, "duplicate operator"))
 93		}
 94		seen[operator] = true
 95	}
 96	e.lines = append(e.lines, lines...)
 97}
 98
 99// Seal closes the open edition for good. Publisher only. It fails unless the
100// edition holds exactly expectedCount candidates (at least one).
101func Seal(cur realm, number int, expectedCount int) {
102	requirePublisher(cur)
103	e := requireOpen(number)
104	if expectedCount < 1 {
105		panic("validator_scores: expectedCount must be at least 1")
106	}
107	if got := len(e.lines); got != expectedCount {
108		panic("validator_scores: edition holds " + strconv.Itoa(got) + " candidates, expected " + strconv.Itoa(expectedCount))
109	}
110	e.sealed = true
111}
112
113// DiscardOpen drops the open edition. Publisher only. Its number is reused
114// by the next OpenEdition. The slot is cleared first so the backing array
115// does not keep the discarded edition stored.
116func DiscardOpen(cur realm) {
117	requirePublisher(cur)
118	if openEdition() == nil {
119		panic("validator_scores: no open edition")
120	}
121	editions[len(editions)-1] = nil
122	editions = editions[:len(editions)-1]
123}