mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
mpt: strip branch if 1 child is left
If the child left is a hash node, we should retrieve it from store. Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
d2e8254848
commit
9691eee10c
2 changed files with 61 additions and 14 deletions
|
@ -38,8 +38,10 @@ func (b *Batch) Add(key []byte, value []byte) {
|
||||||
// PutBatch puts batch to trie.
|
// PutBatch puts batch to trie.
|
||||||
// It is not atomic (and probably cannot be without substantial slow-down)
|
// It is not atomic (and probably cannot be without substantial slow-down)
|
||||||
// and returns number of elements processed.
|
// and returns number of elements processed.
|
||||||
// However each element is being put atomically, so Trie is always in a valid state.
|
// If an error is returned, the trie may be in the inconsistent state in case of storage failures.
|
||||||
// It is used mostly after the block processing to update MPT and error is not expected.
|
// This is due to the fact that we can remove multiple children from the branch node simultaneously
|
||||||
|
// and won't strip the resulting branch node.
|
||||||
|
// However it is used mostly after the block processing to update MPT and error is not expected.
|
||||||
func (t *Trie) PutBatch(b Batch) (int, error) {
|
func (t *Trie) PutBatch(b Batch) (int, error) {
|
||||||
r, n, err := t.putBatch(b.kv)
|
r, n, err := t.putBatch(b.kv)
|
||||||
t.root = r
|
t.root = r
|
||||||
|
@ -74,23 +76,31 @@ func (t *Trie) putBatchIntoBranch(curr *BranchNode, kv []keyValue) (Node, int, e
|
||||||
return t.addToBranch(curr, kv, true)
|
return t.addToBranch(curr, kv, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trie) mergeExtension(prefix []byte, sub Node) Node {
|
func (t *Trie) mergeExtension(prefix []byte, sub Node) (Node, error) {
|
||||||
switch sn := sub.(type) {
|
switch sn := sub.(type) {
|
||||||
case *ExtensionNode:
|
case *ExtensionNode:
|
||||||
t.removeRef(sn.Hash(), sn.bytes)
|
t.removeRef(sn.Hash(), sn.bytes)
|
||||||
sn.key = append(prefix, sn.key...)
|
sn.key = append(prefix, sn.key...)
|
||||||
sn.invalidateCache()
|
sn.invalidateCache()
|
||||||
t.addRef(sn.Hash(), sn.bytes)
|
t.addRef(sn.Hash(), sn.bytes)
|
||||||
return sn
|
return sn, nil
|
||||||
case *HashNode:
|
case *HashNode:
|
||||||
return sn
|
if sn.IsEmpty() {
|
||||||
|
return sn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := t.getFromStore(sn.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return sn, err
|
||||||
|
}
|
||||||
|
return t.mergeExtension(prefix, n)
|
||||||
default:
|
default:
|
||||||
if len(prefix) != 0 {
|
if len(prefix) != 0 {
|
||||||
e := NewExtensionNode(prefix, sub)
|
e := NewExtensionNode(prefix, sub)
|
||||||
t.addRef(e.Hash(), e.bytes)
|
t.addRef(e.Hash(), e.bytes)
|
||||||
return e
|
return e, nil
|
||||||
}
|
}
|
||||||
return sub
|
return sub, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,13 +113,19 @@ func (t *Trie) putBatchIntoExtension(curr *ExtensionNode, kv []keyValue) (Node,
|
||||||
// Extension must be split into new nodes.
|
// Extension must be split into new nodes.
|
||||||
stripPrefix(len(curr.key), kv)
|
stripPrefix(len(curr.key), kv)
|
||||||
sub, n, err := t.putBatchIntoNode(curr.next, kv)
|
sub, n, err := t.putBatchIntoNode(curr.next, kv)
|
||||||
return t.mergeExtension(pref, sub), n, err
|
if err == nil {
|
||||||
|
sub, err = t.mergeExtension(pref, sub)
|
||||||
|
}
|
||||||
|
return sub, n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pref) != 0 {
|
if len(pref) != 0 {
|
||||||
stripPrefix(len(pref), kv)
|
stripPrefix(len(pref), kv)
|
||||||
sub, n, err := t.putBatchIntoExtensionNoPrefix(curr.key[len(pref):], curr.next, kv)
|
sub, n, err := t.putBatchIntoExtensionNoPrefix(curr.key[len(pref):], curr.next, kv)
|
||||||
return t.mergeExtension(pref, sub), n, err
|
if err == nil {
|
||||||
|
sub, err = t.mergeExtension(pref, sub)
|
||||||
|
}
|
||||||
|
return sub, n, err
|
||||||
}
|
}
|
||||||
return t.putBatchIntoExtensionNoPrefix(curr.key, curr.next, kv)
|
return t.putBatchIntoExtensionNoPrefix(curr.key, curr.next, kv)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +150,15 @@ func (t *Trie) addToBranch(b *BranchNode, kv []keyValue, inTrie bool) (Node, int
|
||||||
if inTrie {
|
if inTrie {
|
||||||
t.removeRef(b.Hash(), b.bytes)
|
t.removeRef(b.Hash(), b.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Error during iterate means some storage failure (i.e. some hash node cannot be
|
||||||
|
// retrieved from storage). This can leave trie in inconsistent state, because
|
||||||
|
// it can be impossible to strip branch node after it has been changed.
|
||||||
|
// Consider a branch with 10 children, first 9 of which are deleted and the remaining one
|
||||||
|
// is a leaf node replaced by a hash node missing from storage.
|
||||||
|
// This can't be fixed easily because we need to _revert_ changes in reference counts
|
||||||
|
// for children which were updated successfully. But storage access errors means we are
|
||||||
|
// in a bad state anyway.
|
||||||
n, err := t.iterateBatch(kv, func(c byte, kv []keyValue) (int, error) {
|
n, err := t.iterateBatch(kv, func(c byte, kv []keyValue) (int, error) {
|
||||||
child, n, err := t.putBatchIntoNode(b.Children[c], kv)
|
child, n, err := t.putBatchIntoNode(b.Children[c], kv)
|
||||||
b.Children[c] = child
|
b.Children[c] = child
|
||||||
|
@ -142,12 +167,19 @@ func (t *Trie) addToBranch(b *BranchNode, kv []keyValue, inTrie bool) (Node, int
|
||||||
if inTrie && n != 0 {
|
if inTrie && n != 0 {
|
||||||
b.invalidateCache()
|
b.invalidateCache()
|
||||||
}
|
}
|
||||||
return t.stripBranch(b), n, err
|
|
||||||
|
// Even if some of the children can't be put, we need to try to strip branch
|
||||||
|
// and possibly update refcounts.
|
||||||
|
nd, bErr := t.stripBranch(b)
|
||||||
|
if err == nil {
|
||||||
|
err = bErr
|
||||||
|
}
|
||||||
|
return nd, n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// stripsBranch strips branch node after incomplete batch put.
|
// stripsBranch strips branch node after incomplete batch put.
|
||||||
// It assumes there is no reference to b in trie.
|
// It assumes there is no reference to b in trie.
|
||||||
func (t *Trie) stripBranch(b *BranchNode) Node {
|
func (t *Trie) stripBranch(b *BranchNode) (Node, error) {
|
||||||
var n int
|
var n int
|
||||||
var lastIndex byte
|
var lastIndex byte
|
||||||
for i := range b.Children {
|
for i := range b.Children {
|
||||||
|
@ -158,12 +190,12 @@ func (t *Trie) stripBranch(b *BranchNode) Node {
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case n == 0:
|
case n == 0:
|
||||||
return new(HashNode)
|
return new(HashNode), nil
|
||||||
case n == 1:
|
case n == 1:
|
||||||
return t.mergeExtension([]byte{lastIndex}, b.Children[lastIndex])
|
return t.mergeExtension([]byte{lastIndex}, b.Children[lastIndex])
|
||||||
default:
|
default:
|
||||||
t.addRef(b.Hash(), b.bytes)
|
t.addRef(b.Hash(), b.bytes)
|
||||||
return b
|
return b, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +259,10 @@ func (t *Trie) newSubTrieMany(prefix []byte, kv []keyValue, value []byte) (Node,
|
||||||
b.Children[lastChild] = leaf
|
b.Children[lastChild] = leaf
|
||||||
}
|
}
|
||||||
nd, n, err := t.addToBranch(b, kv, false)
|
nd, n, err := t.addToBranch(b, kv, false)
|
||||||
return t.mergeExtension(prefix, nd), n, err
|
if err == nil {
|
||||||
|
nd, err = t.mergeExtension(prefix, nd)
|
||||||
|
}
|
||||||
|
return nd, n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripPrefix(n int, kv []keyValue) {
|
func stripPrefix(n int, kv []keyValue) {
|
||||||
|
|
|
@ -162,6 +162,18 @@ func TestTrie_PutBatchBranch(t *testing.T) {
|
||||||
tr1, tr2 := prepareBranch(t)
|
tr1, tr2 := prepareBranch(t)
|
||||||
var ps = pairs{{[]byte{0x00, 2}, nil}}
|
var ps = pairs{{[]byte{0x00, 2}, nil}}
|
||||||
testPut(t, ps, tr1, tr2)
|
testPut(t, ps, tr1, tr2)
|
||||||
|
|
||||||
|
t.Run("non-empty child is hash node", func(t *testing.T) {
|
||||||
|
tr1, tr2 := prepareBranch(t)
|
||||||
|
tr1.Flush()
|
||||||
|
tr1.Collapse(1)
|
||||||
|
tr2.Flush()
|
||||||
|
tr2.Collapse(1)
|
||||||
|
|
||||||
|
var ps = pairs{{[]byte{0x00, 2}, nil}}
|
||||||
|
testPut(t, ps, tr1, tr2)
|
||||||
|
require.IsType(t, (*ExtensionNode)(nil), tr1.root)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
t.Run("incomplete put, transform to extension", func(t *testing.T) {
|
t.Run("incomplete put, transform to extension", func(t *testing.T) {
|
||||||
tr1, tr2 := prepareBranch(t)
|
tr1, tr2 := prepareBranch(t)
|
||||||
|
|
Loading…
Reference in a new issue