-
Notifications
You must be signed in to change notification settings - Fork 4
/
node_encoders.go
144 lines (119 loc) · 4.84 KB
/
node_encoders.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
package smt
import (
"bytes"
"encoding/binary"
)
// TODO_TECHDEBT: All of the parsing, encoding and checking functions in this file
// can be abstracted out into the `trieNode` interface.
// TODO_IMPROVE: We should create well-defined structs for every type of node
// to streamline the process of encoding & encoding and to improve readability.
// If decoding needs to be language agnostic (to implement POKT clients), in other
// languages, protobufs should be considered. If decoding does not need to be
// language agnostic, we can use Go's gob package for more efficient serialization.
// NB: In this file, all references to the variable `data` should be treated as `encodedNodeData`.
// It was abbreviated to `data` for brevity.
// TODO_TECHDEBT: We can easily use `iota` and ENUMS to create a wait to have
// more expressive code, and leverage switches statements throughout.
var (
leafNodePrefix = []byte{0}
innerNodePrefix = []byte{1}
extNodePrefix = []byte{2}
prefixLen = 1
)
// NB: We use `prefixLen` a lot through this file, so to make the code more readable, we
// define it as a constant but need to assert on its length just in case the code evolves
// in the future.
func init() {
if len(leafNodePrefix) != prefixLen ||
len(innerNodePrefix) != prefixLen ||
len(extNodePrefix) != prefixLen {
panic("invalid prefix length")
}
}
// isLeafNode returns true if the encoded node data is a leaf node
func isLeafNode(data []byte) bool {
return bytes.Equal(data[:prefixLen], leafNodePrefix)
}
// isExtNode returns true if the encoded node data is an extension node
func isExtNode(data []byte) bool {
return bytes.Equal(data[:prefixLen], extNodePrefix)
}
// isInnerNode returns true if the encoded node data is an inner node
func isInnerNode(data []byte) bool {
return bytes.Equal(data[:prefixLen], innerNodePrefix)
}
// encodeLeafNode encodes leaf nodes. This function applies to both the SMT and
// SMST since the weight of the node is appended to the end of the valueHash.
func encodeLeafNode(path, leafData []byte) (data []byte) {
data = append(data, leafNodePrefix...)
data = append(data, path...)
data = append(data, leafData...)
return
}
// encodeInnerNode encodes inner node given the data for both children
func encodeInnerNode(leftData, rightData []byte) (data []byte) {
data = append(data, innerNodePrefix...)
data = append(data, leftData...)
data = append(data, rightData...)
return
}
// encodeExtensionNode encodes the data of an extension nodes
func encodeExtensionNode(pathBounds [2]byte, path, childData []byte) (data []byte) {
data = append(data, extNodePrefix...)
data = append(data, pathBounds[:]...)
data = append(data, path...)
data = append(data, childData...)
return
}
// encodeSumInnerNode encodes an inner node for an smst given the data for both children
func encodeSumInnerNode(leftData, rightData []byte) (data []byte) {
leftSum, leftCount := parseSumAndCount(leftData)
rightSum, rightCount := parseSumAndCount(rightData)
// Compute the SumBz of the current node
var SumBz [sumSizeBytes]byte
binary.BigEndian.PutUint64(SumBz[:], leftSum+rightSum)
// Compute the count of the current node
var countBz [countSizeBytes]byte
binary.BigEndian.PutUint64(countBz[:], leftCount+rightCount)
// Prepare and return the encoded inner node data
data = encodeInnerNode(leftData, rightData)
data = append(data, SumBz[:]...)
data = append(data, countBz[:]...)
return
}
// encodeSumExtensionNode encodes the data of a sum extension node
func encodeSumExtensionNode(pathBounds [2]byte, path, childData []byte) (data []byte) {
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(childData)
// Compute the sumBz of the current node
var sumBz [sumSizeBytes]byte
copy(sumBz[:], childData[firstSumByteIdx:firstCountByteIdx])
// Compute the count of the current node
var countBz [countSizeBytes]byte
copy(countBz[:], childData[firstCountByteIdx:])
// Prepare and return the encoded inner node data
data = encodeExtensionNode(pathBounds, path, childData)
data = append(data, sumBz[:]...)
data = append(data, countBz[:]...)
return
}
// checkPrefix panics if the prefix of the data does not match the expected prefix
func checkPrefix(data, prefix []byte) {
if !bytes.Equal(data[:prefixLen], prefix) {
panic("invalid prefix")
}
}
// parseSum parses the sum from the encoded node data
func parseSumAndCount(data []byte) (sum, count uint64) {
firstSumByteIdx, firstCountByteIdx := getFirstMetaByteIdx(data)
sumBz := data[firstSumByteIdx:firstCountByteIdx]
if !bytes.Equal(sumBz, defaultEmptySum[:]) {
// TODO_CONSIDERATION: We chose BigEndian for readability but most computers
// now are optimized for LittleEndian encoding could be a micro optimization one day.`
sum = binary.BigEndian.Uint64(sumBz)
}
countBz := data[firstCountByteIdx:]
if !bytes.Equal(countBz, defaultEmptyCount[:]) {
count = binary.BigEndian.Uint64(countBz)
}
return
}