trie: simplify StackTrie implementation (#23950)

Trim the search key from head as it's being pushed deeper into the trie. Previously the search key was never modified but each node kept information how to slice and compare it in keyOffset. Now the keyOffset is not needed as this information is included in the slice of the search key. This way the keyOffset can be removed and key manipulation
simplified.
pull/24021/head
Paweł Bylica 3 years ago committed by GitHub
parent c10a0a62c3
commit 86fe359a56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 72
      trie/stacktrie.go

@ -54,12 +54,11 @@ func returnToPool(st *StackTrie) {
// in order. Once it determines that a subtree will no longer be inserted // in order. Once it determines that a subtree will no longer be inserted
// into, it will hash it and free up the memory it uses. // into, it will hash it and free up the memory it uses.
type StackTrie struct { type StackTrie struct {
nodeType uint8 // node type (as in branch, ext, leaf) nodeType uint8 // node type (as in branch, ext, leaf)
val []byte // value contained by this node if it's a leaf val []byte // value contained by this node if it's a leaf
key []byte // key chunk covered by this (full|ext) node key []byte // key chunk covered by this (leaf|ext) node
keyOffset int // offset of the key chunk inside a full key children [16]*StackTrie // list of children (for branch and exts)
children [16]*StackTrie // list of children (for fullnodes and exts) db ethdb.KeyValueWriter // Pointer to the commit db, can be nil
db ethdb.KeyValueWriter // Pointer to the commit db, can be nil
} }
// NewStackTrie allocates and initializes an empty trie. // NewStackTrie allocates and initializes an empty trie.
@ -90,15 +89,13 @@ func (st *StackTrie) MarshalBinary() (data []byte, err error) {
w = bufio.NewWriter(&b) w = bufio.NewWriter(&b)
) )
if err := gob.NewEncoder(w).Encode(struct { if err := gob.NewEncoder(w).Encode(struct {
Nodetype uint8 Nodetype uint8
Val []byte Val []byte
Key []byte Key []byte
KeyOffset uint8
}{ }{
st.nodeType, st.nodeType,
st.val, st.val,
st.key, st.key,
uint8(st.keyOffset),
}); err != nil { }); err != nil {
return nil, err return nil, err
} }
@ -126,16 +123,14 @@ func (st *StackTrie) UnmarshalBinary(data []byte) error {
func (st *StackTrie) unmarshalBinary(r io.Reader) error { func (st *StackTrie) unmarshalBinary(r io.Reader) error {
var dec struct { var dec struct {
Nodetype uint8 Nodetype uint8
Val []byte Val []byte
Key []byte Key []byte
KeyOffset uint8
} }
gob.NewDecoder(r).Decode(&dec) gob.NewDecoder(r).Decode(&dec)
st.nodeType = dec.Nodetype st.nodeType = dec.Nodetype
st.val = dec.Val st.val = dec.Val
st.key = dec.Key st.key = dec.Key
st.keyOffset = int(dec.KeyOffset)
var hasChild = make([]byte, 1) var hasChild = make([]byte, 1)
for i := range st.children { for i := range st.children {
@ -160,20 +155,18 @@ func (st *StackTrie) setDb(db ethdb.KeyValueWriter) {
} }
} }
func newLeaf(ko int, key, val []byte, db ethdb.KeyValueWriter) *StackTrie { func newLeaf(key, val []byte, db ethdb.KeyValueWriter) *StackTrie {
st := stackTrieFromPool(db) st := stackTrieFromPool(db)
st.nodeType = leafNode st.nodeType = leafNode
st.keyOffset = ko st.key = append(st.key, key...)
st.key = append(st.key, key[ko:]...)
st.val = val st.val = val
return st return st
} }
func newExt(ko int, key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie { func newExt(key []byte, child *StackTrie, db ethdb.KeyValueWriter) *StackTrie {
st := stackTrieFromPool(db) st := stackTrieFromPool(db)
st.nodeType = extNode st.nodeType = extNode
st.keyOffset = ko st.key = append(st.key, key...)
st.key = append(st.key, key[ko:]...)
st.children[0] = child st.children[0] = child
return st return st
} }
@ -211,17 +204,18 @@ func (st *StackTrie) Reset() {
st.children[i] = nil st.children[i] = nil
} }
st.nodeType = emptyNode st.nodeType = emptyNode
st.keyOffset = 0
} }
// Helper function that, given a full key, determines the index // Helper function that, given a full key, determines the index
// at which the chunk pointed by st.keyOffset is different from // at which the chunk pointed by st.keyOffset is different from
// the same chunk in the full key. // the same chunk in the full key.
func (st *StackTrie) getDiffIndex(key []byte) int { func (st *StackTrie) getDiffIndex(key []byte) int {
diffindex := 0 for idx, nibble := range st.key {
for ; diffindex < len(st.key) && st.key[diffindex] == key[st.keyOffset+diffindex]; diffindex++ { if nibble != key[idx] {
return idx
}
} }
return diffindex return len(st.key)
} }
// Helper function to that inserts a (key, value) pair into // Helper function to that inserts a (key, value) pair into
@ -229,7 +223,7 @@ func (st *StackTrie) getDiffIndex(key []byte) int {
func (st *StackTrie) insert(key, value []byte) { func (st *StackTrie) insert(key, value []byte) {
switch st.nodeType { switch st.nodeType {
case branchNode: /* Branch */ case branchNode: /* Branch */
idx := int(key[st.keyOffset]) idx := int(key[0])
// Unresolve elder siblings // Unresolve elder siblings
for i := idx - 1; i >= 0; i-- { for i := idx - 1; i >= 0; i-- {
if st.children[i] != nil { if st.children[i] != nil {
@ -241,10 +235,10 @@ func (st *StackTrie) insert(key, value []byte) {
} }
// Add new child // Add new child
if st.children[idx] == nil { if st.children[idx] == nil {
st.children[idx] = stackTrieFromPool(st.db) st.children[idx] = newLeaf(key[1:], value, st.db)
st.children[idx].keyOffset = st.keyOffset + 1 } else {
st.children[idx].insert(key[1:], value)
} }
st.children[idx].insert(key, value)
case extNode: /* Ext */ case extNode: /* Ext */
// Compare both key chunks and see where they differ // Compare both key chunks and see where they differ
diffidx := st.getDiffIndex(key) diffidx := st.getDiffIndex(key)
@ -257,7 +251,7 @@ func (st *StackTrie) insert(key, value []byte) {
if diffidx == len(st.key) { if diffidx == len(st.key) {
// Ext key and key segment are identical, recurse into // Ext key and key segment are identical, recurse into
// the child node. // the child node.
st.children[0].insert(key, value) st.children[0].insert(key[diffidx:], value)
return return
} }
// Save the original part. Depending if the break is // Save the original part. Depending if the break is
@ -266,7 +260,7 @@ func (st *StackTrie) insert(key, value []byte) {
// node directly. // node directly.
var n *StackTrie var n *StackTrie
if diffidx < len(st.key)-1 { if diffidx < len(st.key)-1 {
n = newExt(diffidx+1, st.key, st.children[0], st.db) n = newExt(st.key[diffidx+1:], st.children[0], st.db)
} else { } else {
// Break on the last byte, no need to insert // Break on the last byte, no need to insert
// an extension node: reuse the current node // an extension node: reuse the current node
@ -288,15 +282,14 @@ func (st *StackTrie) insert(key, value []byte) {
// node. // node.
st.children[0] = stackTrieFromPool(st.db) st.children[0] = stackTrieFromPool(st.db)
st.children[0].nodeType = branchNode st.children[0].nodeType = branchNode
st.children[0].keyOffset = st.keyOffset + diffidx
p = st.children[0] p = st.children[0]
} }
// Create a leaf for the inserted part // Create a leaf for the inserted part
o := newLeaf(st.keyOffset+diffidx+1, key, value, st.db) o := newLeaf(key[diffidx+1:], value, st.db)
// Insert both child leaves where they belong: // Insert both child leaves where they belong:
origIdx := st.key[diffidx] origIdx := st.key[diffidx]
newIdx := key[diffidx+st.keyOffset] newIdx := key[diffidx]
p.children[origIdx] = n p.children[origIdx] = n
p.children[newIdx] = o p.children[newIdx] = o
st.key = st.key[:diffidx] st.key = st.key[:diffidx]
@ -330,7 +323,6 @@ func (st *StackTrie) insert(key, value []byte) {
st.nodeType = extNode st.nodeType = extNode
st.children[0] = NewStackTrie(st.db) st.children[0] = NewStackTrie(st.db)
st.children[0].nodeType = branchNode st.children[0].nodeType = branchNode
st.children[0].keyOffset = st.keyOffset + diffidx
p = st.children[0] p = st.children[0]
} }
@ -339,11 +331,11 @@ func (st *StackTrie) insert(key, value []byte) {
// The child leave will be hashed directly in order to // The child leave will be hashed directly in order to
// free up some memory. // free up some memory.
origIdx := st.key[diffidx] origIdx := st.key[diffidx]
p.children[origIdx] = newLeaf(diffidx+1, st.key, st.val, st.db) p.children[origIdx] = newLeaf(st.key[diffidx+1:], st.val, st.db)
p.children[origIdx].hash() p.children[origIdx].hash()
newIdx := key[diffidx+st.keyOffset] newIdx := key[diffidx]
p.children[newIdx] = newLeaf(p.keyOffset+1, key, value, st.db) p.children[newIdx] = newLeaf(key[diffidx+1:], value, st.db)
// Finally, cut off the key part that has been passed // Finally, cut off the key part that has been passed
// over to the children. // over to the children.
@ -351,7 +343,7 @@ func (st *StackTrie) insert(key, value []byte) {
st.val = nil st.val = nil
case emptyNode: /* Empty */ case emptyNode: /* Empty */
st.nodeType = leafNode st.nodeType = leafNode
st.key = key[st.keyOffset:] st.key = key
st.val = value st.val = value
case hashedNode: case hashedNode:
panic("trying to insert into hash") panic("trying to insert into hash")

Loading…
Cancel
Save