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

provable_test.gno

4.68 Kb · 165 lines
  1package provable
  2
  3import (
  4	"strconv"
  5	"strings"
  6	"testing"
  7
  8	"gno.land/p/moul/x/merkle/v0"
  9)
 10
 11func TestAppendAndVerify(cur realm, t *testing.T) {
 12	seed()
 13	i := Append(cross(cur), "a fourth commitment")
 14	if i != 3 || Size() != 4 {
 15		t.Fatalf("Append returned %d with Size %d, want 3 and 4", i, Size())
 16	}
 17	if Entry(i) != "a fourth commitment" {
 18		t.Errorf("Entry(%d) = %q", i, Entry(i))
 19	}
 20
 21	root := Root()
 22	for j := 0; j < Size(); j++ {
 23		path, before, after := ProofOf(j)
 24		if !Verify(root, j, Size(), Entry(j), path, before, after) {
 25			t.Errorf("entry %d: its own proof does not verify", j)
 26		}
 27	}
 28}
 29
 30// Append trims, so the proof must be over the trimmed entry.
 31func TestAppendTrims(cur realm, t *testing.T) {
 32	seed()
 33	i := Append(cross(cur), "  padded  ")
 34	if Entry(i) != "padded" {
 35		t.Fatalf("Entry = %q, want %q", Entry(i), "padded")
 36	}
 37	path, before, after := ProofOf(i)
 38	if !Verify(Root(), i, Size(), "padded", path, before, after) {
 39		t.Error("trimmed entry does not verify")
 40	}
 41	if Verify(Root(), i, Size(), "  padded  ", path, before, after) {
 42		t.Error("the untrimmed string verified, so the leaf is not what is stored")
 43	}
 44}
 45
 46func TestVerifyRejects(cur realm, t *testing.T) {
 47	seed()
 48	root := Root()
 49	path, before, after := ProofOf(1)
 50	other := "0000000000000000000000000000000000000000000000000000000000000000"
 51
 52	tests := []struct {
 53		name                       string
 54		root                       string
 55		index, total               int
 56		entry, path, before, after string
 57	}{
 58		{"wrong entry", root, 1, 3, "not in the log", path, before, after},
 59		{"another real entry", root, 1, 3, Entry(0), path, before, after},
 60		{"wrong index", root, 0, 3, Entry(1), path, before, after},
 61		{"wrong total", root, 1, 4, Entry(1), path, before, after},
 62		{"wrong root", other, 1, 3, Entry(1), path, before, after},
 63		{"root not hex", "zz", 1, 3, Entry(1), path, before, after},
 64		{"root wrong length", "abcd", 1, 3, Entry(1), path, before, after},
 65		{"empty path", root, 1, 3, Entry(1), "", before, after},
 66		{"peaks swapped", root, 1, 3, Entry(1), path, after, before},
 67		{"garbage path", root, 1, 3, Entry(1), "zz", before, after},
 68	}
 69	for _, tc := range tests {
 70		if Verify(tc.root, tc.index, tc.total, tc.entry, tc.path, tc.before, tc.after) {
 71			t.Errorf("%s: accepted, must be rejected", tc.name)
 72		}
 73	}
 74}
 75
 76// The log root is also the Tendermint simple-tree root over the same entries,
 77// so the fixed-leaf-set proof encoding verifies against it too. This is the
 78// claim the realm makes in Render; it is tested, not asserted.
 79func TestVerifyFixedAcceptsTendermintProof(cur realm, t *testing.T) {
 80	seed()
 81	Append(cross(cur), "a fourth commitment")
 82	Append(cross(cur), "a fifth commitment")
 83
 84	leaves := make([][]byte, Size())
 85	for i := range leaves {
 86		leaves[i] = []byte(Entry(i))
 87	}
 88	tree := merkle.New(leaves)
 89	if tree.RootHex() != Root() {
 90		t.Fatalf("tree root %s, log root %s", tree.RootHex(), Root())
 91	}
 92	for i := 0; i < Size(); i++ {
 93		p, err := tree.Proof(i)
 94		if err != nil {
 95			t.Fatalf("i=%d: %v", i, err)
 96		}
 97		if !VerifyFixed(Root(), i, Size(), Entry(i), p.Hex()) {
 98			t.Errorf("i=%d: Tendermint proof rejected by the realm", i)
 99		}
100		if VerifyFixed(Root(), i, Size(), "forged", p.Hex()) {
101			t.Errorf("i=%d: forged leaf accepted", i)
102		}
103	}
104}
105
106func TestCaps(t *testing.T) {
107	seed()
108	tests := []struct {
109		name  string
110		entry string
111	}{
112		{"empty", ""},
113		{"whitespace only", "   "},
114		{"too long", strings.Repeat("x", MaxEntryLen+1)},
115	}
116	for _, tc := range tests {
117		if got := panicked(func() { appendEntry(tc.entry) }); got == "" {
118			t.Errorf("%s: accepted, must panic", tc.name)
119		}
120	}
121	// A note of exactly MaxEntryLen is fine.
122	if got := panicked(func() { appendEntry(strings.Repeat("x", MaxEntryLen)) }); got != "" {
123		t.Errorf("entry of exactly MaxEntryLen panicked: %s", got)
124	}
125}
126
127func TestLogIsBounded(t *testing.T) {
128	seed()
129	for Size() < MaxEntries {
130		appendEntry("filler-" + strconv.Itoa(Size()))
131	}
132	if got := panicked(func() { appendEntry("one too many") }); got == "" {
133		t.Error("appending past MaxEntries was accepted")
134	}
135	seed()
136}
137
138func TestEntryRangeChecked(t *testing.T) {
139	seed()
140	for _, i := range []int{-1, 3, 999} {
141		if got := panicked(func() { Entry(i) }); got == "" {
142			t.Errorf("Entry(%d) did not panic", i)
143		}
144		if got := panicked(func() { ProofOf(i) }); got == "" {
145			t.Errorf("ProofOf(%d) did not panic", i)
146		}
147	}
148}
149
150// panicked runs f and returns the panic message, or "" if it returned
151// normally. Safe here because these calls are in-package and do not cross a
152// realm boundary, where a panic would be an unrecoverable abort instead.
153func panicked(f func()) (msg string) {
154	defer func() {
155		if r := recover(); r != nil {
156			if s, ok := r.(string); ok {
157				msg = s
158			} else {
159				msg = "panic"
160			}
161		}
162	}()
163	f()
164	return ""
165}