Skip to content

Commit 456001a

Browse files
committed
Rewrite tree to use lazy-loaded cache with deferred commit
This introduces a largely rewritten implementation of the SMT which operates on a cached, lazily-loaded tree structure which must be explicitly committed to DB, replacing the existing pattern of performing DB reads and writes on for each operation. This gives a significant performance improvement on all operations while preserving backwards compatibility of commitment hashes. This also: * refactors the hasher objects to allow tree paths and stored values to be hashed independently, and Options to configure them. By passing an identity function as the path hasher, the raw key can be used directly as the leaf. * removes value storage from the tree; the caller must maintain their own value mapping. * removes the DeepSubtree code supporting state-transition fraud proofs, as it was not compatible as-is with this implementation (but there should be no technical block to adapting it to this pattern). * renames various types and decouples the path and value hashing functions. * expands tests to cover corner cases, e.g. orphan node removal.
1 parent dba215c commit 456001a

19 files changed

+1267
-1614
lines changed

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,21 @@ import (
1919
)
2020

2121
func main() {
22-
// Initialise two new key-value store to store the nodes and values of the tree
22+
// Initialise two new key-value store to store the nodes of the tree
23+
// (Note: the tree only stores hashed values, not raw value data)
2324
nodeStore := smt.NewSimpleMap()
24-
valueStore := smt.NewSimpleMap()
2525
// Initialise the tree
26-
tree := smt.NewSparseMerkleTree(nodeStore, valueStore, sha256.New())
26+
tree := smt.NewSMT(nodeStore, sha256.New())
2727

2828
// Update the key "foo" with the value "bar"
29-
_, _ = tree.Update([]byte("foo"), []byte("bar"))
29+
_ = tree.Update([]byte("foo"), []byte("bar"))
3030

3131
// Generate a Merkle proof for foo=bar
3232
proof, _ := tree.Prove([]byte("foo"))
3333
root := tree.Root() // We also need the current tree root for the proof
3434

3535
// Verify the Merkle proof for foo=bar
36-
if smt.VerifyProof(proof, root, []byte("foo"), []byte("bar"), sha256.New()) {
36+
if smt.VerifyProof(proof, root, []byte("foo"), []byte("bar"), tree.Spec()) {
3737
fmt.Println("Proof verification succeeded.")
3838
} else {
3939
fmt.Println("Proof verification failed.")

bench_test.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,29 @@ import (
88

99
func BenchmarkSparseMerkleTree_Update(b *testing.B) {
1010
smn, smv := NewSimpleMap(), NewSimpleMap()
11-
smt := NewSparseMerkleTree(smn, smv, sha256.New())
11+
smt := NewSMTWithStorage(smn, smv, sha256.New())
1212

1313
b.ResetTimer()
1414
b.ReportAllocs()
1515
for i := 0; i < b.N; i++ {
1616
s := strconv.Itoa(i)
17-
_, _ = smt.Update([]byte(s), []byte(s))
17+
_ = smt.Update([]byte(s), []byte(s))
1818
}
1919
}
2020

2121
func BenchmarkSparseMerkleTree_Delete(b *testing.B) {
2222
smn, smv := NewSimpleMap(), NewSimpleMap()
23-
smt := NewSparseMerkleTree(smn, smv, sha256.New())
23+
smt := NewSMTWithStorage(smn, smv, sha256.New())
2424

2525
for i := 0; i < 100000; i++ {
2626
s := strconv.Itoa(i)
27-
_, _ = smt.Update([]byte(s), []byte(s))
27+
_ = smt.Update([]byte(s), []byte(s))
2828
}
2929

3030
b.ResetTimer()
3131
b.ReportAllocs()
3232
for i := 0; i < b.N; i++ {
3333
s := strconv.Itoa(i)
34-
_, _ = smt.Delete([]byte(s))
34+
_ = smt.Delete([]byte(s))
3535
}
3636
}

bulk_test.go

+53-54
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,36 @@ import (
44
"bytes"
55
"crypto/sha256"
66
"math/rand"
7-
"reflect"
87
"testing"
98
)
109

10+
type opCounts struct{ ops, inserts, updates, deletes int }
11+
type bulkop struct{ key, val []byte }
12+
1113
// Test all tree operations in bulk.
12-
func TestSparseMerkleTree(t *testing.T) {
13-
for i := 0; i < 5; i++ {
14+
func TestBulkOperations(t *testing.T) {
15+
rand.Seed(1)
16+
17+
cases := []opCounts{
1418
// Test more inserts/updates than deletions.
15-
bulkOperations(t, 200, 100, 100, 50)
16-
}
17-
for i := 0; i < 5; i++ {
19+
{200, 100, 100, 50},
20+
{1000, 100, 100, 50},
1821
// Test extreme deletions.
19-
bulkOperations(t, 200, 100, 100, 500)
22+
{200, 100, 100, 500},
23+
{1000, 100, 100, 500},
24+
}
25+
for _, tc := range cases {
26+
bulkOperations(t, tc.ops, tc.inserts, tc.updates, tc.deletes)
2027
}
2128
}
2229

2330
// Test all tree operations in bulk, with specified ratio probabilities of insert, update and delete.
2431
func bulkOperations(t *testing.T, operations int, insert int, update int, delete int) {
2532
smn, smv := NewSimpleMap(), NewSimpleMap()
26-
smt := NewSparseMerkleTree(smn, smv, sha256.New())
33+
smt := NewSMTWithStorage(smn, smv, sha256.New())
2734

2835
max := insert + update + delete
29-
kv := make(map[string]string)
36+
var kv []bulkop
3037

3138
for i := 0; i < operations; i++ {
3239
n := rand.Intn(max)
@@ -39,98 +46,90 @@ func bulkOperations(t *testing.T, operations int, insert int, update int, delete
3946
val := make([]byte, valLen)
4047
rand.Read(val)
4148

42-
kv[string(key)] = string(val)
43-
_, err := smt.Update(key, val)
49+
err := smt.Update(key, val)
4450
if err != nil {
45-
t.Errorf("error: %v", err)
51+
t.Fatalf("error: %v", err)
4652
}
53+
kv = append(kv, bulkop{key, val})
4754
} else if n > insert && n < insert+update { // Update
48-
keys := reflect.ValueOf(kv).MapKeys()
49-
if len(keys) == 0 {
55+
if len(kv) == 0 {
5056
continue
5157
}
52-
key := []byte(keys[rand.Intn(len(keys))].Interface().(string))
53-
58+
ki := rand.Intn(len(kv))
5459
valLen := 1 + rand.Intn(64)
5560
val := make([]byte, valLen)
5661
rand.Read(val)
5762

58-
kv[string(key)] = string(val)
59-
_, err := smt.Update(key, val)
63+
err := smt.Update(kv[ki].key, val)
6064
if err != nil {
61-
t.Errorf("error: %v", err)
65+
t.Fatalf("error: %v", err)
6266
}
67+
kv[ki].val = val
6368
} else { // Delete
64-
keys := reflect.ValueOf(kv).MapKeys()
65-
if len(keys) == 0 {
69+
if len(kv) == 0 {
6670
continue
6771
}
68-
key := []byte(keys[rand.Intn(len(keys))].Interface().(string))
72+
ki := rand.Intn(len(kv))
6973

70-
kv[string(key)] = ""
71-
_, err := smt.Update(key, defaultValue)
72-
if err != nil {
73-
t.Errorf("error: %v", err)
74+
err := smt.Delete(kv[ki].key)
75+
if err != nil && err != ErrKeyNotPresent {
76+
t.Fatalf("error: %v", err)
7477
}
78+
kv[ki].val = defaultValue
7579
}
76-
77-
bulkCheckAll(t, smt, &kv)
7880
}
81+
bulkCheckAll(t, smt, kv)
7982
}
8083

81-
func bulkCheckAll(t *testing.T, smt *SparseMerkleTree, kv *map[string]string) {
82-
for k, v := range *kv {
83-
value, err := smt.Get([]byte(k))
84+
func bulkCheckAll(t *testing.T, smt *SMTWithStorage, kv []bulkop) {
85+
for ki := range kv {
86+
k, v := kv[ki].key, kv[ki].val
87+
88+
value, err := smt.GetValue([]byte(k))
8489
if err != nil {
8590
t.Errorf("error: %v", err)
8691
}
8792
if !bytes.Equal([]byte(v), value) {
88-
t.Error("got incorrect value when bulk testing operations")
93+
t.Errorf("Incorrect value (i=%d)", ki)
8994
}
9095

9196
// Generate and verify a Merkle proof for this key.
9297
proof, err := smt.Prove([]byte(k))
9398
if err != nil {
9499
t.Errorf("error: %v", err)
95100
}
96-
if !VerifyProof(proof, smt.Root(), []byte(k), []byte(v), smt.th.hasher) {
97-
t.Error("Merkle proof failed to verify")
101+
if !VerifyProof(proof, smt.Root(), []byte(k), []byte(v), smt.Spec()) {
102+
t.Fatalf("Merkle proof failed to verify (i=%d): %v", ki, []byte(k))
98103
}
99-
compactProof, err := smt.ProveCompact([]byte(k))
104+
compactProof, err := ProveCompact([]byte(k), smt)
100105
if err != nil {
101106
t.Errorf("error: %v", err)
102107
}
103-
if !VerifyCompactProof(compactProof, smt.Root(), []byte(k), []byte(v), smt.th.hasher) {
104-
t.Error("Merkle proof failed to verify")
108+
if !VerifyCompactProof(compactProof, smt.Root(), []byte(k), []byte(v), smt.Spec()) {
109+
t.Fatalf("Compact Merkle proof failed to verify (i=%d): %v", ki, []byte(k))
105110
}
106111

107-
if v == "" {
112+
if v == nil {
108113
continue
109114
}
110115

111116
// Check that the key is at the correct height in the tree.
112117
largestCommonPrefix := 0
113-
for k2, v2 := range *kv {
114-
if v2 == "" {
118+
for ki2 := range kv {
119+
k2, v2 := kv[ki2].key, kv[ki2].val
120+
if v2 == nil {
115121
continue
116122
}
117-
commonPrefix := countCommonPrefix(smt.th.path([]byte(k)), smt.th.path([]byte(k2)))
118-
if commonPrefix != smt.depth() && commonPrefix > largestCommonPrefix {
123+
124+
ph := smt.Spec().ph
125+
commonPrefix := countCommonPrefix(ph.Path([]byte(k)), ph.Path([]byte(k2)), 0)
126+
if commonPrefix != smt.Spec().depth() && commonPrefix > largestCommonPrefix {
119127
largestCommonPrefix = commonPrefix
120128
}
121129
}
122-
sideNodes, _, _, _, err := smt.sideNodesForRoot(smt.th.path([]byte(k)), smt.Root(), false)
123-
if err != nil {
124-
t.Errorf("error: %v", err)
125-
}
126-
numSideNodes := 0
127-
for _, v := range sideNodes {
128-
if v != nil {
129-
numSideNodes++
130-
}
131-
}
132-
if numSideNodes != largestCommonPrefix+1 && (numSideNodes != 0 && largestCommonPrefix != 0) {
133-
t.Error("leaf is at unexpected height")
130+
if len(proof.SideNodes) != largestCommonPrefix+1 &&
131+
(len(proof.SideNodes) != 0 && largestCommonPrefix != 0) {
132+
t.Errorf("leaf is at unexpected height (ki=%d)", ki)
134133
}
135134
}
136135
}

deepsubtree.go

-129
This file was deleted.

0 commit comments

Comments
 (0)