trie: ensure dirty flag is unset for embedded child nodes

This was caught by the new invariant check.
This commit is contained in:
Felix Lange 2016-10-17 23:01:29 +02:00
parent 44f419ec0f
commit 8d56bf5ceb
2 changed files with 42 additions and 32 deletions

View File

@ -75,23 +75,20 @@ func (h *hasher) hash(n node, db DatabaseWriter, force bool) (node, node, error)
if err != nil { if err != nil {
return hashNode{}, n, err return hashNode{}, n, err
} }
// Cache the hash of the ndoe for later reuse. // Cache the hash of the ndoe for later reuse and remove
if hash, ok := hashed.(hashNode); ok && !force { // the dirty flag in commit mode. It's fine to assign these values directly
switch cached := cached.(type) { // without copying the node first because hashChildren copies it.
cachedHash, _ := hashed.(hashNode)
switch cn := cached.(type) {
case *shortNode: case *shortNode:
cached = cached.copy() cn.flags.hash = cachedHash
cached.flags.hash = hash
if db != nil { if db != nil {
cached.flags.dirty = false cn.flags.dirty = false
} }
return hashed, cached, nil
case *fullNode: case *fullNode:
cached = cached.copy() cn.flags.hash = cachedHash
cached.flags.hash = hash
if db != nil { if db != nil {
cached.flags.dirty = false cn.flags.dirty = false
}
return hashed, cached, nil
} }
} }
return hashed, cached, nil return hashed, cached, nil

View File

@ -462,32 +462,45 @@ func runRandTest(rt randTest) bool {
return false return false
} }
case opCheckCacheInvariant: case opCheckCacheInvariant:
return checkCacheInvariant(tr.root, tr.cachegen, 0) return checkCacheInvariant(tr.root, nil, tr.cachegen, false, 0)
} }
} }
return true return true
} }
func checkCacheInvariant(n node, parentCachegen uint16, depth int) bool { func checkCacheInvariant(n, parent node, parentCachegen uint16, parentDirty bool, depth int) bool {
var children []node
var flag nodeFlag
switch n := n.(type) { switch n := n.(type) {
case *shortNode: case *shortNode:
if n.flags.gen > parentCachegen { flag = n.flags
fmt.Printf("cache invariant violation: %d > %d\nat depth %d node %s", n.flags.gen, parentCachegen, depth, spew.Sdump(n)) children = []node{n.Val}
return false
}
return checkCacheInvariant(n.Val, n.flags.gen, depth+1)
case *fullNode: case *fullNode:
if n.flags.gen > parentCachegen { flag = n.flags
fmt.Printf("cache invariant violation: %d > %d\nat depth %d node %s", n.flags.gen, parentCachegen, depth, spew.Sdump(n)) children = n.Children[:]
return false default:
}
for _, child := range n.Children {
if !checkCacheInvariant(child, n.flags.gen, depth+1) {
return false
}
}
return true return true
} }
showerror := func() {
fmt.Printf("at depth %d node %s", depth, spew.Sdump(n))
fmt.Printf("parent: %s", spew.Sdump(parent))
}
if flag.gen > parentCachegen {
fmt.Printf("cache invariant violation: %d > %d\n", flag.gen, parentCachegen)
showerror()
return false
}
if depth > 0 && !parentDirty && flag.dirty {
fmt.Printf("cache invariant violation: child is dirty but parent isn't\n")
showerror()
return false
}
for _, child := range children {
if !checkCacheInvariant(child, n, flag.gen, flag.dirty, depth+1) {
return false
}
}
return true return true
} }