-
Notifications
You must be signed in to change notification settings - Fork 704
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
merkledb
-- dynamic root
#2177
merkledb
-- dynamic root
#2177
Changes from 160 commits
45a8a3b
c1c2d71
fb832f7
1866331
8616169
4f14e23
5d9a7c1
1067d7c
f73f6d2
f41a29f
fc64ec3
ce62579
a4b76f4
4275b07
01b9f4b
6bf8ce2
5ae830d
a64dc47
fd8ea46
6f9f0cf
a6d0549
af3f9f7
98e6028
614cd66
9f975f3
d3538a8
e84d83e
92cd323
888d9b9
01668e5
a6c5e1f
c5e8c4d
5a56753
a9cbbf2
9d7477d
34380f2
82c0061
14def09
8a41266
41e0d8d
37b12a4
b75987f
b1e71e1
2f55d7b
c23286d
04ff880
53d49fe
ae615c1
71fb895
27c8229
c5a3c2a
a0fec24
e4ea07d
d57eac4
1adbac1
6db69f3
47dd688
3b89531
d6c3344
860bd8a
40049b5
3b4b83a
52c9857
0f6cc05
3c9fc2b
605b294
07aee5c
ee838f0
d9bfef9
404132b
e8b3a9b
f669d3d
e52172d
96e6cec
43318e4
992f628
a7faed4
11ee267
19f9690
ae6bd64
52176b7
530b6a5
6a676c2
e84d1b2
bece10c
d730b53
c9efc4a
bb70bca
9fc9045
7f142f8
08b27ea
545df4a
74ce294
a140e42
f938ae7
ddf27ae
babe99d
f5c333b
7e7c7b9
eb157fd
525e33d
bb649ea
d7548ce
63dd9b9
7e48fc4
d1e0c5e
a972cad
b8ef383
85f557e
7054ec2
61502bf
a24650b
c62eb91
348e766
2f401af
72747e1
e554992
dc78849
974d8a3
a0473ea
41c0b5f
7ea00cb
271ef1b
ebb4486
70d88a5
0ae7028
f7d401b
b041f53
192b2fd
83aca7e
778e172
7ad352e
9b7fc3a
35672c3
7a351bd
20e4a04
14e0e97
9bf30c5
fcf0d22
8c5d762
5b8d473
bb45d69
bcd5e94
2b45f69
c6d3b95
4650bdd
f6f37ca
dbdf325
8cdcdaf
12a507e
53f2bcb
48c3b9d
da6a670
4c5d79c
f143063
44e6667
7b99e6a
09427b4
3fcf63b
9583691
8431791
33c9fd7
464602a
d351095
6207461
8546169
429dbc3
ad2d2f1
7963a0b
f7eb1ec
afa14f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -51,11 +51,11 @@ var ( | |
intermediateNodePrefix = []byte{2} | ||
|
||
cleanShutdownKey = []byte(string(metadataPrefix) + "cleanShutdown") | ||
rootDBKey = []byte(string(metadataPrefix) + "root") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. realized belatedly that you don't actually need this since the root should always be the smallest key present in your nodes db. You should be able to just grab the first key on the iterator, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could do that, but I feel like this is a bit cleaner than having to check both the value and intermediate node databases There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current code still checks both dbs |
||
hadCleanShutdown = []byte{1} | ||
didNotHaveCleanShutdown = []byte{0} | ||
|
||
errSameRoot = errors.New("start and end root are the same") | ||
errNoNewSentinel = errors.New("there was no updated sentinel node in change list") | ||
errSameRoot = errors.New("start and end root are the same") | ||
) | ||
|
||
type ChangeProofer interface { | ||
|
@@ -64,6 +64,9 @@ type ChangeProofer interface { | |
// Returns at most [maxLength] key/value pairs. | ||
// Returns [ErrInsufficientHistory] if this node has insufficient history | ||
// to generate the proof. | ||
// Returns ErrEmptyProof if [endRootID] is ids.Empty. | ||
// Note that [endRootID] == ids.Empty means the trie is empty | ||
// (i.e. we don't need a change proof.) | ||
GetChangeProof( | ||
ctx context.Context, | ||
startRootID ids.ID, | ||
|
@@ -100,6 +103,9 @@ type RangeProofer interface { | |
// [start, end] when the root of the trie was [rootID]. | ||
// If [start] is Nothing, there's no lower bound on the range. | ||
// If [end] is Nothing, there's no upper bound on the range. | ||
// Returns ErrEmptyProof if [rootID] is ids.Empty. | ||
// Note that [rootID] == ids.Empty means the trie is empty | ||
// (i.e. we don't need a range proof.) | ||
GetRangeProofAtRoot( | ||
ctx context.Context, | ||
rootID ids.ID, | ||
|
@@ -201,11 +207,11 @@ type merkleDB struct { | |
debugTracer trace.Tracer | ||
infoTracer trace.Tracer | ||
|
||
// The sentinel node of this trie. | ||
// It is the node with a nil key and is the ancestor of all nodes in the trie. | ||
// If it has a value or has multiple children, it is also the root of the trie. | ||
sentinelNode *node | ||
rootID ids.ID | ||
// The root of this trie. | ||
// Nothing if the trie is empty. | ||
root maybe.Maybe[*node] | ||
|
||
rootID ids.ID | ||
|
||
// Valid children of this trie. | ||
childViews []*trieView | ||
|
@@ -268,6 +274,9 @@ func newDatabase( | |
// add current root to history (has no changes) | ||
trieDB.history.record(&changeSummary{ | ||
rootID: trieDB.rootID, | ||
rootChange: change[maybe.Maybe[*node]]{ | ||
after: trieDB.root, | ||
}, | ||
values: map[Key]*change[maybe.Maybe[[]byte]]{}, | ||
nodes: map[Key]*change[*node]{}, | ||
}) | ||
|
@@ -295,7 +304,8 @@ func newDatabase( | |
// Deletes every intermediate node and rebuilds them by re-adding every key/value. | ||
// TODO: make this more efficient by only clearing out the stale portions of the trie. | ||
func (db *merkleDB) rebuild(ctx context.Context, cacheSize int) error { | ||
db.sentinelNode = newNode(Key{}) | ||
db.root = maybe.Nothing[*node]() | ||
db.rootID = ids.Empty | ||
|
||
// Delete intermediate nodes. | ||
if err := database.ClearPrefix(db.baseDB, intermediateNodePrefix, rebuildIntermediateDeletionWriteSize); err != nil { | ||
|
@@ -581,13 +591,6 @@ func (db *merkleDB) getMerkleRoot() ids.ID { | |
return db.rootID | ||
} | ||
|
||
// isSentinelNodeTheRoot returns true if the passed in sentinel node has a value and or multiple child nodes | ||
// When this is true, the root of the trie is the sentinel node | ||
// When this is false, the root of the trie is the sentinel node's single child | ||
func isSentinelNodeTheRoot(sentinel *node) bool { | ||
return sentinel.valueDigest.HasValue() || len(sentinel.children) != 1 | ||
} | ||
|
||
func (db *merkleDB) GetProof(ctx context.Context, key []byte) (*Proof, error) { | ||
db.commitLock.RLock() | ||
defer db.commitLock.RUnlock() | ||
|
@@ -644,11 +647,13 @@ func (db *merkleDB) getRangeProofAtRoot( | |
end maybe.Maybe[[]byte], | ||
maxLength int, | ||
) (*RangeProof, error) { | ||
if db.closed { | ||
switch { | ||
case db.closed: | ||
return nil, database.ErrClosed | ||
} | ||
if maxLength <= 0 { | ||
case maxLength <= 0: | ||
return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength) | ||
case rootID == ids.Empty: | ||
return nil, ErrEmptyProof | ||
} | ||
|
||
historicalView, err := db.getHistoricalViewForRange(rootID, start, end) | ||
|
@@ -666,11 +671,13 @@ func (db *merkleDB) GetChangeProof( | |
end maybe.Maybe[[]byte], | ||
maxLength int, | ||
) (*ChangeProof, error) { | ||
if start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1 { | ||
switch { | ||
case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) == 1: | ||
return nil, ErrStartAfterEnd | ||
} | ||
if startRootID == endRootID { | ||
case startRootID == endRootID: | ||
return nil, errSameRoot | ||
case endRootID == ids.Empty: | ||
return nil, ErrEmptyProof | ||
} | ||
|
||
db.commitLock.RLock() | ||
|
@@ -931,13 +938,7 @@ func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) e | |
return nil | ||
} | ||
|
||
sentinelChange, ok := changes.nodes[Key{}] | ||
if !ok { | ||
return errNoNewSentinel | ||
} | ||
|
||
currentValueNodeBatch := db.valueNodeDB.NewBatch() | ||
|
||
_, nodesSpan := db.infoTracer.Start(ctx, "MerkleDB.commitChanges.writeNodes") | ||
for key, nodeChange := range changes.nodes { | ||
shouldAddIntermediate := nodeChange.after != nil && !nodeChange.after.hasValue() | ||
|
@@ -973,12 +974,18 @@ func (db *merkleDB) commitChanges(ctx context.Context, trieToCommit *trieView) e | |
return err | ||
} | ||
|
||
// Only modify in-memory state after the commit succeeds | ||
// so that we don't need to clean up on error. | ||
db.sentinelNode = sentinelChange.after | ||
db.rootID = changes.rootID | ||
db.history.record(changes) | ||
return nil | ||
|
||
// Update root in database. | ||
db.root = changes.rootChange.after | ||
db.rootID = changes.rootID | ||
|
||
if db.root.IsNothing() { | ||
return db.baseDB.Delete(rootDBKey) | ||
} | ||
|
||
rootKey := codec.encodeKey(db.root.Value().key) | ||
return db.baseDB.Put(rootDBKey, rootKey) | ||
} | ||
|
||
// moveChildViewsToDB removes any child views from the trieToCommit and moves them to the db | ||
|
@@ -1014,7 +1021,7 @@ func (db *merkleDB) VerifyChangeProof( | |
case start.HasValue() && end.HasValue() && bytes.Compare(start.Value(), end.Value()) > 0: | ||
return ErrStartAfterEnd | ||
case proof.Empty(): | ||
return ErrNoMerkleProof | ||
return ErrEmptyProof | ||
case end.HasValue() && len(proof.KeyChanges) == 0 && len(proof.EndProof) == 0: | ||
// We requested an end proof but didn't get one. | ||
return ErrNoEndProof | ||
|
@@ -1156,37 +1163,41 @@ func (db *merkleDB) invalidateChildrenExcept(exception *trieView) { | |
} | ||
} | ||
|
||
// If the root is on disk, set [db.root] to it. | ||
// Otherwise leave [db.root] as Nothing. | ||
func (db *merkleDB) initializeRoot() error { | ||
// Not sure if the sentinel node exists or if it had a value, | ||
// so check under both prefixes | ||
var err error | ||
db.sentinelNode, err = db.intermediateNodeDB.Get(Key{}) | ||
rootKeyBytes, err := db.baseDB.Get(rootDBKey) | ||
if err != nil { | ||
if !errors.Is(err, database.ErrNotFound) { | ||
return err | ||
} | ||
// Root isn't on disk. | ||
return nil | ||
} | ||
|
||
if errors.Is(err, database.ErrNotFound) { | ||
// Didn't find the sentinel in the intermediateNodeDB, check the valueNodeDB | ||
db.sentinelNode, err = db.valueNodeDB.Get(Key{}) | ||
// Root is on disk. | ||
rootKey, err := codec.decodeKey(rootKeyBytes) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// First, see if root is an intermediate node. | ||
var root *node | ||
root, err = db.getEditableNode(rootKey, false /* hasValue */) | ||
if err != nil { | ||
if !errors.Is(err, database.ErrNotFound) { | ||
return err | ||
} | ||
|
||
// Sentinel node doesn't exist in either database prefix. | ||
// Make a new one and store it in the intermediateNodeDB | ||
db.sentinelNode = newNode(Key{}) | ||
if err := db.intermediateNodeDB.Put(Key{}, db.sentinelNode); err != nil { | ||
// The root must be a value node. | ||
root, err = db.getEditableNode(rootKey, true /* hasValue */) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
db.rootID = db.sentinelNode.calculateID(db.metrics) | ||
if !isSentinelNodeTheRoot(db.sentinelNode) { | ||
// If the sentinel node is not the root, the trie's root is the sentinel node's only child | ||
for _, childEntry := range db.sentinelNode.children { | ||
db.rootID = childEntry.id | ||
} | ||
} | ||
db.rootID = root.calculateID(db.metrics) | ||
db.root = maybe.Some(root) | ||
return nil | ||
} | ||
|
||
|
@@ -1263,12 +1274,15 @@ func (db *merkleDB) getNode(key Key, hasValue bool) (*node, error) { | |
switch { | ||
case db.closed: | ||
return nil, database.ErrClosed | ||
case key == Key{}: | ||
danlaine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return db.sentinelNode, nil | ||
case hasValue: | ||
return db.valueNodeDB.Get(key) | ||
default: | ||
return db.intermediateNodeDB.Get(key) | ||
} | ||
return db.intermediateNodeDB.Get(key) | ||
} | ||
|
||
func (db *merkleDB) getRoot() maybe.Maybe[*node] { | ||
return db.root | ||
} | ||
|
||
func (db *merkleDB) Clear() error { | ||
danlaine marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -1287,13 +1301,13 @@ func (db *merkleDB) Clear() error { | |
} | ||
|
||
// Clear root | ||
db.sentinelNode = newNode(Key{}) | ||
db.rootID = db.sentinelNode.calculateID(db.metrics) | ||
db.root = maybe.Nothing[*node]() | ||
db.rootID = ids.Empty | ||
|
||
// Clear history | ||
db.history = newTrieHistory(db.history.maxHistoryLen) | ||
db.history.record(&changeSummary{ | ||
rootID: db.getMerkleRoot(), | ||
rootID: db.rootID, | ||
values: map[Key]*change[maybe.Maybe[[]byte]]{}, | ||
nodes: map[Key]*change[*node]{}, | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Auto-generation fails because of generics 😭