-
Notifications
You must be signed in to change notification settings - Fork 0
/
database.go
279 lines (240 loc) · 8.97 KB
/
database.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
package trie
import (
"errors"
"github.com/fatzero/mass-core/trie/common"
"github.com/fatzero/mass-core/trie/massdb"
"github.com/fatzero/mass-core/trie/rawdb"
)
// Database is an intermediate write layer between the trie data structures and
// the disk database. The aim is to accumulate trie writes in-memory and only
// periodically flush a couple tries to disk, garbage collecting the remainder.
//
// Note, the trie Database is **not** thread safe in its mutations, but it **is**
// thread safe in providing individual, independent node access. The rationale
// behind this split design is to provide read access to RPC handlers and sync
// servers even while the trie is executing expensive garbage collection.
type Database struct {
diskdb massdb.KeyValueStore // Persistent storage for matured trie nodes
// dirties map[common.Hash]*cachedNode // Data and references relationships of dirty trie nodes
// oldest common.Hash // Oldest tracked node, flush-list head
// newest common.Hash // Newest tracked node, flush-list tail
// gctime time.Duration // Time spent on garbage collection since last commit
// gcnodes uint64 // Nodes garbage collected since last commit
// gcsize common.StorageSize // Data storage garbage collected since last commit
// flushtime time.Duration // Time spent on data flushing since last commit
// flushnodes uint64 // Nodes flushed since last commit
// flushsize common.StorageSize // Data storage flushed since last commit
// dirtiesSize common.StorageSize // Storage size of the dirty node cache (exc. metadata)
// childrenSize common.StorageSize // Storage size of the external children tracking
// preimagesSize common.StorageSize // Storage size of the preimages cache
// lock sync.RWMutex
}
// // rawNode is a simple binary blob used to differentiate between collapsed trie
// // nodes and already encoded RLP binary blobs (while at the same time store them
// // in the same cache fields).
// type rawNode []byte
// func (n rawNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") }
// // rawFullNode represents only the useful data content of a full node, with the
// // caches and flags stripped out to minimize its data storage. This type honors
// // the same RLP encoding as the original parent.
// type rawFullNode [17]node
// func (n rawFullNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") }
// // rawShortNode represents only the useful data content of a short node, with the
// // caches and flags stripped out to minimize its data storage. This type honors
// // the same RLP encoding as the original parent.
// type rawShortNode struct {
// Key []byte
// Val node
// }
// func (n rawShortNode) cache() (hashNode, bool) { panic("this should never end up in a live trie") }
// // cachedNode is all the information we know about a single cached trie node
// // in the memory database write layer.
// type cachedNode struct {
// node node // Cached collapsed trie node, or raw rlp data
// size uint16 // Byte size of the useful cached data
// parents uint32 // Number of live nodes referencing this one
// children map[common.Hash]uint16 // External children referenced by this node
// flushPrev common.Hash // Previous node in the flush-list
// flushNext common.Hash // Next node in the flush-list
// }
// // rlp returns the raw rlp encoded blob of the cached trie node, either directly
// // from the cache, or by regenerating it from the collapsed node.
// func (n *cachedNode) rlp() []byte {
// if node, ok := n.node.(rawNode); ok {
// return node
// }
// blob, err := rlp.EncodeToBytes(n.node)
// if err != nil {
// panic(err)
// }
// return blob
// }
// // obj returns the decoded and expanded trie node, either directly from the cache,
// // or by regenerating it from the rlp encoded blob.
// func (n *cachedNode) obj(hash common.Hash) node {
// if node, ok := n.node.(rawNode); ok {
// return mustDecodeNode(hash[:], node)
// }
// return expandNode(hash[:], n.node)
// }
// // forChilds invokes the callback for all the tracked children of this node,
// // both the implicit ones from inside the node as well as the explicit ones
// // from outside the node.
// func (n *cachedNode) forChilds(onChild func(hash common.Hash)) {
// for child := range n.children {
// onChild(child)
// }
// if _, ok := n.node.(rawNode); !ok {
// forGatherChildren(n.node, onChild)
// }
// }
// // forGatherChildren traverses the node hierarchy of a collapsed storage node and
// // invokes the callback for all the hashnode children.
// func forGatherChildren(n node, onChild func(hash common.Hash)) {
// switch n := n.(type) {
// case *rawShortNode:
// forGatherChildren(n.Val, onChild)
// case rawFullNode:
// for i := 0; i < 16; i++ {
// forGatherChildren(n[i], onChild)
// }
// case hashNode:
// onChild(common.BytesToHash(n))
// case valueNode, nil, rawNode:
// default:
// panic(fmt.Sprintf("unknown node type: %T", n))
// }
// }
// // simplifyNode traverses the hierarchy of an expanded memory node and discards
// // all the internal caches, returning a node that only contains the raw data.
// func simplifyNode(n node) node {
// switch n := n.(type) {
// case *shortNode:
// // Short nodes discard the flags and cascade
// return &rawShortNode{Key: n.Key, Val: simplifyNode(n.Val)}
// case *fullNode:
// // Full nodes discard the flags and cascade
// node := rawFullNode(n.Children)
// for i := 0; i < len(node); i++ {
// if node[i] != nil {
// node[i] = simplifyNode(node[i])
// }
// }
// return node
// case valueNode, hashNode, rawNode:
// return n
// default:
// panic(fmt.Sprintf("unknown node type: %T", n))
// }
// }
// // expandNode traverses the node hierarchy of a collapsed storage node and converts
// // all fields and keys into expanded memory form.
// func expandNode(hash hashNode, n node) node {
// switch n := n.(type) {
// case *rawShortNode:
// // Short nodes need key and child expansion
// return &shortNode{
// Key: compactToHex(n.Key),
// Val: expandNode(nil, n.Val),
// flags: nodeFlag{
// hash: hash,
// },
// }
// case rawFullNode:
// // Full nodes need child expansion
// node := &fullNode{
// flags: nodeFlag{
// hash: hash,
// },
// }
// for i := 0; i < len(node.Children); i++ {
// if n[i] != nil {
// node.Children[i] = expandNode(nil, n[i])
// }
// }
// return node
// case valueNode, hashNode:
// return n
// default:
// panic(fmt.Sprintf("unknown node type: %T", n))
// }
// }
func NewDatabase(diskdb massdb.KeyValueStore) *Database {
db := &Database{
diskdb: diskdb,
// dirties: map[common.Hash]*cachedNode{{}: {
// children: make(map[common.Hash]uint16),
// }},
}
return db
}
// node retrieves a cached trie node from memory, or returns nil if none can be
// found in the memory cache.
func (db *Database) node(hash common.Hash) node {
// // Retrieve the node from the dirty cache if available
// db.lock.RLock()
// dirty := db.dirties[hash]
// db.lock.RUnlock()
// if dirty != nil {
// return dirty.obj(hash)
// }
// Content unavailable in memory, attempt to retrieve from disk
enc, err := db.diskdb.Get(hash[:])
if err != nil || enc == nil {
return nil
}
n, err := decodeNode(hash[:], enc)
if err != nil {
return nil
}
switch v := (n).(type) {
case *fullNode:
v.flags.hash = hash[:]
case *shortNode:
v.flags.hash = hash[:]
}
return n
}
// Node retrieves an encoded cached trie node from memory. If it cannot be found
// cached, the method queries the persistent database for the content.
func (db *Database) Node(hash common.Hash) ([]byte, error) {
// It doesn't make sense to retrieve the metaroot
if hash == (common.Hash{}) {
return nil, errors.New("not found")
}
// Content unavailable in memory, attempt to retrieve from disk
enc := rawdb.ReadTrieNode(db.diskdb, hash)
if len(enc) != 0 {
return enc, nil
}
return nil, errors.New("not found")
}
// // insert inserts a collapsed trie node into the memory database.
// // The blob size must be specified to allow proper size tracking.
// // All nodes inserted by this function will be reference tracked
// // and in theory should only used for **trie nodes** insertion.
// func (db *Database) insert(hash common.Hash, size int, node node) {
// // If the node's already cached, skip
// if _, ok := db.dirties[hash]; ok {
// return
// }
// // Create the cached entry for this node
// entry := &cachedNode{
// node: simplifyNode(node),
// size: uint16(size),
// flushPrev: db.newest,
// }
// entry.forChilds(func(child common.Hash) {
// if c := db.dirties[child]; c != nil {
// c.parents++
// }
// })
// db.dirties[hash] = entry
// // Update the flush-list endpoints
// if db.oldest == (common.Hash{}) {
// db.oldest, db.newest = hash, hash
// } else {
// db.dirties[db.newest].flushNext, db.newest = hash, hash
// }
// db.dirtiesSize += common.StorageSize(common.HashLength + entry.size)
// }