diff --git a/trie/bintrie/stem_node.go b/trie/bintrie/stem_node.go index 6d6b261f37..93c55acefa 100644 --- a/trie/bintrie/stem_node.go +++ b/trie/bintrie/stem_node.go @@ -51,8 +51,15 @@ func (sn *StemNode) allValues() [][]byte { return sn.values[:] } +// setValue mutates a value slot and marks the stem for re-hash and +// re-flush. This is the only API for post-load value mutation; direct +// values[...] writes are reserved for the on-disk load path in +// decodeNode, which must leave mustRecompute/dirty at their loaded +// state. func (sn *StemNode) setValue(suffix byte, value []byte) { sn.values[suffix] = value + sn.mustRecompute = true + sn.dirty = true } func (sn *StemNode) Hash() common.Hash { diff --git a/trie/bintrie/store_ops.go b/trie/bintrie/store_ops.go index 23e3e4520e..32e294cb50 100644 --- a/trie/bintrie/store_ops.go +++ b/trie/bintrie/store_ops.go @@ -199,12 +199,10 @@ func (s *NodeStore) insertValuesAtStem(ref nodeRef, stem []byte, values [][]byte case kindStem: sn := s.getStem(ref.Index()) if sn.Stem == [StemSize]byte(stem[:StemSize]) { - // Same stem — merge values + // Same stem — merge values (setValue marks dirty+mustRecompute) for i, v := range values { if v != nil { sn.setValue(byte(i), v) - sn.mustRecompute = true - sn.dirty = true } } return ref, nil @@ -233,7 +231,8 @@ func (s *NodeStore) insertValuesAtStem(ref nodeRef, stem []byte, values [][]byte return s.insertValuesAtStem(resolved, stem, values, resolver, depth) case kindEmpty: - // Create new StemNode + // Create new StemNode. Flag flips before the value loop so an + // all-nil values input still marks the newly-created stem dirty. stemIdx := s.allocStem() sn := s.getStem(stemIdx) copy(sn.Stem[:], stem[:StemSize]) @@ -242,7 +241,7 @@ func (s *NodeStore) insertValuesAtStem(ref nodeRef, stem []byte, values [][]byte sn.dirty = true for i, v := range values { if v != nil { - sn.values[i] = v + sn.setValue(byte(i), v) } } return makeRef(kindStem, stemIdx), nil