-
Notifications
You must be signed in to change notification settings - Fork 4
/
trie_spec.go
309 lines (269 loc) · 9.1 KB
/
trie_spec.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package smt
import (
"encoding/binary"
"hash"
)
// TrieSpec specifies the hashing functions used by a trie instance to encode
// leaf paths and stored values, and the corresponding maximum trie depth.
type TrieSpec struct {
th trieHasher
ph PathHasher
vh ValueHasher
sumTrie bool
}
// NewTrieSpec returns a new TrieSpec with the given hasher and sumTrie flag
func NewTrieSpec(
hasher hash.Hash,
sumTrie bool,
opts ...TrieSpecOption,
) TrieSpec {
spec := TrieSpec{th: *NewTrieHasher(hasher)}
spec.ph = &pathHasher{spec.th}
spec.vh = &valueHasher{spec.th}
spec.sumTrie = sumTrie
for _, opt := range opts {
opt(&spec)
}
return spec
}
// Spec returns the TrieSpec associated with the given trie
func (spec *TrieSpec) Spec() *TrieSpec {
return spec
}
// PathHasherSize returns the length (in bytes) of digests produced by the
// path hasher
func (spec *TrieSpec) PathHasherSize() int { return spec.ph.PathSize() }
// placeholder returns the default placeholder value depending on the trie type
func (spec *TrieSpec) placeholder() []byte {
if spec.sumTrie {
placeholder := spec.th.placeholder()
placeholder = append(placeholder, defaultEmptySum[:]...)
placeholder = append(placeholder, defaultEmptyCount[:]...)
return placeholder
}
return spec.th.placeholder()
}
// hashSize returns the hash size depending on the trie type
func (spec *TrieSpec) hashSize() int {
if spec.sumTrie {
return spec.th.hashSize() + sumSizeBytes + countSizeBytes
}
return spec.th.hashSize()
}
// digestLeaf returns the hash and preimage of a leaf node depending on the trie type
func (spec *TrieSpec) digestLeaf(path, value []byte) ([]byte, []byte) {
if spec.sumTrie {
return spec.th.digestSumLeafNode(path, value)
}
return spec.th.digestLeafNode(path, value)
}
// digestNode returns the hash and preimage of a node depending on the trie type
func (spec *TrieSpec) digestInnerNode(left, right []byte) ([]byte, []byte) {
if spec.sumTrie {
return spec.th.digestSumInnerNode(left, right)
}
return spec.th.digestInnerNode(left, right)
}
// digest hashes a node depending on the trie type
func (spec *TrieSpec) digest(node trieNode) []byte {
if spec.sumTrie {
return spec.digestSumNode(node)
}
return spec.digestNode(node)
}
// encode serializes a node depending on the trie type
func (spec *TrieSpec) encode(node trieNode) []byte {
if spec.sumTrie {
return spec.encodeSumNode(node)
}
return spec.encodeNode(node)
}
// hashPreimage hashes the serialised data provided depending on the trie type
func (spec *TrieSpec) hashPreimage(data []byte) []byte {
if spec.sumTrie {
return spec.hashSumSerialization(data)
}
return spec.hashSerialization(data)
}
// Used for verification of serialized proof data
func (spec *TrieSpec) hashSerialization(data []byte) []byte {
if isExtNode(data) {
pathBounds, path, childHash := spec.parseExtNode(data)
ext := extensionNode{path: path, child: &lazyNode{childHash}}
copy(ext.pathBounds[:], pathBounds)
return spec.digestNode(&ext)
}
return spec.th.digestData(data)
}
// Used for verification of serialized proof data for sum trie nodes
func (spec *TrieSpec) hashSumSerialization(data []byte) []byte {
if isExtNode(data) {
pathBounds, path, childHash, _, _ := spec.parseSumExtNode(data)
ext := extensionNode{path: path, child: &lazyNode{childHash}}
copy(ext.pathBounds[:], pathBounds)
return spec.digestSumNode(&ext)
}
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(data)
digest := spec.th.digestData(data)
digest = append(digest, data[firstSumByteIdx:firstCountByteIdx]...)
digest = append(digest, data[firstCountByteIdx:]...)
return digest
}
// depth returns the maximum depth of the trie.
// Since this tree is a binary tree, the depth is the number of bits in the path
// TODO_UPNEXT(@Olshansk):: Try to understand why we're not taking the log of the output
func (spec *TrieSpec) depth() int {
return spec.ph.PathSize() * 8 // path size is in bytes so multiply by 8 to get num bits
}
// valueHash returns the hash of a value, or the value itself if no value hasher is specified.
func (spec *TrieSpec) valueHash(value []byte) []byte {
if spec.vh == nil {
return value
}
return spec.vh.HashValue(value)
}
// encodeNode serializes a node into a byte slice
func (spec *TrieSpec) encodeNode(node trieNode) []byte {
switch n := node.(type) {
case *lazyNode:
panic("Encoding a lazyNode is not supported")
case *leafNode:
return encodeLeafNode(n.path, n.valueHash)
case *innerNode:
leftChild := spec.digestNode(n.leftChild)
rightChild := spec.digestNode(n.rightChild)
return encodeInnerNode(leftChild, rightChild)
case *extensionNode:
child := spec.digestNode(n.child)
return encodeExtensionNode(n.pathBounds, n.path, child)
default:
panic("Unknown node type")
}
}
// digestNode hashes a node and returns its digest
func (spec *TrieSpec) digestNode(node trieNode) []byte {
if node == nil {
return spec.th.placeholder()
}
var cachedDigest *[]byte
switch n := node.(type) {
case *lazyNode:
return n.digest
case *leafNode:
cachedDigest = &n.digest
case *innerNode:
cachedDigest = &n.digest
case *extensionNode:
if n.digest == nil {
n.digest = spec.digestNode(n.expand())
}
return n.digest
}
if *cachedDigest == nil {
*cachedDigest = spec.th.digestData(spec.encodeNode(node))
}
return *cachedDigest
}
// encodeSumNode serializes a sum node and returns the preImage hash.
func (spec *TrieSpec) encodeSumNode(node trieNode) (preImage []byte) {
switch n := node.(type) {
case *lazyNode:
panic("encodeSumNode(lazyNode)")
case *leafNode:
return encodeLeafNode(n.path, n.valueHash)
case *innerNode:
leftChild := spec.digestSumNode(n.leftChild)
rightChild := spec.digestSumNode(n.rightChild)
return encodeSumInnerNode(leftChild, rightChild)
case *extensionNode:
child := spec.digestSumNode(n.child)
return encodeSumExtensionNode(n.pathBounds, n.path, child)
}
return nil
}
// digestSumNode hashes a sum node returning its digest in the following form: [node hash]+[8 byte sum]+[8 byte count]
func (spec *TrieSpec) digestSumNode(node trieNode) []byte {
if node == nil {
return spec.placeholder()
}
var cache *[]byte
switch n := node.(type) {
case *lazyNode:
return n.digest
case *leafNode:
cache = &n.digest
case *innerNode:
cache = &n.digest
case *extensionNode:
if n.digest == nil {
n.digest = spec.digestSumNode(n.expand())
}
return n.digest
}
if *cache == nil {
preImage := spec.encodeSumNode(node)
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(preImage)
*cache = spec.th.digestData(preImage)
*cache = append(*cache, preImage[firstSumByteIdx:firstCountByteIdx]...)
*cache = append(*cache, preImage[firstCountByteIdx:]...)
}
return *cache
}
// parseLeafNode parses a leafNode into its components
func (spec *TrieSpec) parseLeafNode(data []byte) (path, value []byte) {
// panics if not a leaf node
checkPrefix(data, leafNodePrefix)
path = data[prefixLen : prefixLen+spec.ph.PathSize()]
value = data[prefixLen+spec.ph.PathSize():]
return
}
// parseExtNode parses an extNode into its components
func (spec *TrieSpec) parseExtNode(data []byte) (pathBounds, path, childData []byte) {
// panics if not an extension node
checkPrefix(data, extNodePrefix)
// +2 represents the length of the pathBounds
pathBounds = data[prefixLen : prefixLen+2]
path = data[prefixLen+2 : prefixLen+2+spec.ph.PathSize()]
childData = data[prefixLen+2+spec.ph.PathSize():]
return
}
// parseSumLeafNode parses a leafNode and returns its weight as well
// // nolint: unused
func (spec *TrieSpec) parseSumLeafNode(data []byte) (path, value []byte, weight, count uint64) {
// panics if not a leaf node
checkPrefix(data, leafNodePrefix)
path = data[prefixLen : prefixLen+spec.ph.PathSize()]
value = data[prefixLen+spec.ph.PathSize():]
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(data)
// Extract the sum from the encoded node data
var weightBz [sumSizeBytes]byte
copy(weightBz[:], data[firstSumByteIdx:firstCountByteIdx])
binary.BigEndian.PutUint64(weightBz[:], weight)
// Extract the count from the encoded node data
var countBz [countSizeBytes]byte
copy(countBz[:], value[firstCountByteIdx:])
binary.BigEndian.PutUint64(countBz[:], count)
if count != 1 {
panic("count for leaf node should always be 1")
}
return
}
// parseSumExtNode parses the pathBounds, path, child data and sum from the encoded extension node data
func (spec *TrieSpec) parseSumExtNode(data []byte) (pathBounds, path, childData []byte, sum, count uint64) {
// panics if not an extension node
checkPrefix(data, extNodePrefix)
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(data)
// Extract the sum from the encoded node data
var sumBz [sumSizeBytes]byte
copy(sumBz[:], data[firstSumByteIdx:firstCountByteIdx])
binary.BigEndian.PutUint64(sumBz[:], sum)
// Extract the count from the encoded node data
var countBz [countSizeBytes]byte
copy(countBz[:], data[firstCountByteIdx:])
binary.BigEndian.PutUint64(countBz[:], count)
// +2 represents the length of the pathBounds
pathBounds = data[prefixLen : prefixLen+2]
path = data[prefixLen+2 : prefixLen+2+spec.ph.PathSize()]
childData = data[prefixLen+2+spec.ph.PathSize() : firstSumByteIdx]
return
}