supersede.gno
3.24 Kb · 133 lines
1package kourt
2import (
3 "strconv"
4 "strings"
5 bptree "gno.land/p/nt/bptree/v0"
6)
7const (
8 maxSupIn = 8
9 maxSupInPerAuthor = 2
10)
11type supEdge struct {
12 to uint64
13 author address
14 at int64
15}
16func ensureSup(c *Court) {
17 if c.supOf == nil {
18 c.supOf = bptree.NewBPTree32()
19 }
20 if c.supBy == nil {
21 c.supBy = bptree.NewBPTree32()
22 }
23}
24func supersedeOrdered(from, to *claimState) bool {
25 if from.openedAtTime != 0 && to.openedAtTime != 0 {
26 return from.openedAtTime >= to.openedAtTime+deadClaimSecs
27 }
28 return from.openedAt >= to.openedAt+deadClaimTimeout
29}
30func SupersedeClaim(cur realm, courtSlug string, from, to uint64) {
31 if !cur.IsCurrent() {
32 panic(errStaleRealm)
33 }
34 who := cur.Previous().Address()
35 c := mustCourt(courtSlug)
36 if from == to {
37 panic("kourtv2: a claim cannot supersede itself")
38 }
39 f := mustClaim(c, from)
40 t := mustClaim(c, to)
41 if f.author != who {
42 cm := ensureMod(c)
43 if !cm.members.Has(who.String()) {
44 panic("kourtv2: only the re-filing claim's author or a court moderator may record it")
45 }
46 requireActiveMod(cm, who)
47 }
48 if !t.closed {
49 panic("kourtv2: only a claim that died unanswered can be superseded")
50 }
51 if !supersedeOrdered(f, t) {
52 panic("kourtv2: a re-filing must come after the claim it re-files could have died")
53 }
54 ensureSup(c)
55 if c.supOf.Get(beClaimKey(from)) != nil {
56 panic("kourtv2: that claim already re-files one; remove that edge first")
57 }
58 inN, mine := 0, 0
59 p := beClaimKey(to)
60 c.supBy.Iterate(p, "", func(key string, v any) bool {
61 if !strings.HasPrefix(key, p) {
62 return true
63 }
64 inN++
65 if e, ok := v.(*supEdge); ok && e.author == who {
66 mine++
67 }
68 return false
69 })
70 if inN >= maxSupIn {
71 panic("kourtv2: that claim has already been re-filed " + strconv.Itoa(maxSupIn) + " times")
72 }
73 if mine >= maxSupInPerAuthor {
74 panic("kourtv2: you already record " + strconv.Itoa(maxSupInPerAuthor) +
75 " re-filings of that claim")
76 }
77 e := &supEdge{to: to, author: who, at: heightNow()}
78 c.supOf.Set(beClaimKey(from), e)
79 c.supBy.Set(beClaimKey(to)+beClaimKey(from), e)
80}
81func RemoveSupersede(cur realm, courtSlug string, from uint64) {
82 if !cur.IsCurrent() {
83 panic(errStaleRealm)
84 }
85 who := cur.Previous().Address()
86 c := mustCourt(courtSlug)
87 if c.supOf == nil {
88 panic("kourtv2: that claim re-files nothing")
89 }
90 v := c.supOf.Get(beClaimKey(from))
91 if v == nil {
92 panic("kourtv2: that claim re-files nothing")
93 }
94 e := v.(*supEdge)
95 requireEdgeRemover(c, e.author, who)
96 c.supOf.Remove(beClaimKey(from))
97 c.supBy.Remove(beClaimKey(e.to) + beClaimKey(from))
98}
99func ClaimSupersedes(courtSlug string, claimID uint64) string {
100 c := mustCourt(courtSlug)
101 mustClaim(c, claimID)
102 var b strings.Builder
103 b.WriteString("of:")
104 if c.supOf != nil {
105 if v := c.supOf.Get(beClaimKey(claimID)); v != nil {
106 e := v.(*supEdge)
107 if !assocTextGone(c, e.to) {
108 b.WriteString(strconv.FormatUint(e.to, 10))
109 }
110 }
111 }
112 b.WriteString(";by:")
113 if c.supBy != nil {
114 p := beClaimKey(claimID)
115 n := 0
116 c.supBy.Iterate(p, "", func(key string, v any) bool {
117 if !strings.HasPrefix(key, p) {
118 return true
119 }
120 other := claimIDFromKey(key[8:])
121 if assocTextGone(c, other) {
122 return false
123 }
124 if n > 0 {
125 b.WriteString(",")
126 }
127 b.WriteString(strconv.FormatUint(other, 10))
128 n++
129 return false
130 })
131 }
132 return b.String()
133}