mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 09:51:36 +00:00
trie/bintrie: mark stems created via Empty.Insert* as dirty
Empty.Insert and Empty.InsertValuesAtStem construct a fresh StemNode with mustRecompute=true but left the new `dirty` field at its zero value. With the skip-clean CollectNodes optimization enabled, the resulting stem was treated as already-persisted and never flushed to disk. A parent InternalNode's blob would be written referencing a hash for which no blob existed, causing "missing trie node" errors on subsequent reads. This is the path hit whenever a key is inserted into an Empty subtree — the common case on the first insert, and frequently thereafter on splits that leave one side Empty. A long-running deployment surfaced the bug after ~15 hours of random ERC20 writes. Add `dirty: true` to both struct literals, and add regression guards TestEmptyInsertMarksDirty / TestEmptyInsertValuesAtStemMarksDirty that assert the returned stem is dirty.
This commit is contained in:
parent
fad11d5795
commit
f57dd20461
2 changed files with 44 additions and 0 deletions
|
|
@ -36,6 +36,7 @@ func (e Empty) Insert(key []byte, value []byte, _ NodeResolverFn, depth int) (Bi
|
|||
Values: values[:],
|
||||
depth: depth,
|
||||
mustRecompute: true,
|
||||
dirty: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -58,6 +59,7 @@ func (e Empty) InsertValuesAtStem(key []byte, values [][]byte, _ NodeResolverFn,
|
|||
Values: values,
|
||||
depth: depth,
|
||||
mustRecompute: true,
|
||||
dirty: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -220,3 +220,45 @@ func TestEmptyGetHeight(t *testing.T) {
|
|||
t.Errorf("Expected height 0 for empty node, got %d", height)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyInsertMarksDirty verifies that a StemNode produced by Empty.Insert
|
||||
// is marked dirty. Without this, CollectNodes would skip the freshly created
|
||||
// stem and its blob would never reach disk, producing "missing trie node"
|
||||
// errors on subsequent reads.
|
||||
func TestEmptyInsertMarksDirty(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
key[0] = 0xaa
|
||||
val := make([]byte, 32)
|
||||
val[0] = 0xbb
|
||||
n, err := Empty{}.Insert(key, val, nil, 0)
|
||||
if err != nil {
|
||||
t.Fatalf("Insert: %v", err)
|
||||
}
|
||||
sn, ok := n.(*StemNode)
|
||||
if !ok {
|
||||
t.Fatalf("expected *StemNode, got %T", n)
|
||||
}
|
||||
if !sn.dirty {
|
||||
t.Fatalf("stem produced by Empty.Insert must have dirty=true")
|
||||
}
|
||||
}
|
||||
|
||||
// TestEmptyInsertValuesAtStemMarksDirty is the analogous guard for the
|
||||
// bulk-insert entry point. Fresh stems created here must be dirty.
|
||||
func TestEmptyInsertValuesAtStemMarksDirty(t *testing.T) {
|
||||
key := make([]byte, 32)
|
||||
key[0] = 0xcc
|
||||
values := make([][]byte, 256)
|
||||
values[0] = make([]byte, 32)
|
||||
n, err := Empty{}.InsertValuesAtStem(key, values, nil, 3)
|
||||
if err != nil {
|
||||
t.Fatalf("InsertValuesAtStem: %v", err)
|
||||
}
|
||||
sn, ok := n.(*StemNode)
|
||||
if !ok {
|
||||
t.Fatalf("expected *StemNode, got %T", n)
|
||||
}
|
||||
if !sn.dirty {
|
||||
t.Fatalf("stem produced by Empty.InsertValuesAtStem must have dirty=true")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue