Skip to content

Commit fb387c9

Browse files
committed
trie: fix data race around node deletion
1 parent e1bb804 commit fb387c9

File tree

1 file changed

+30
-31
lines changed

1 file changed

+30
-31
lines changed

trie/sync.go

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ type CodeSyncResult struct {
150150
type nodeOp struct {
151151
owner common.Hash // identifier of the trie (empty for account trie)
152152
path []byte // path from the root to the specified node.
153-
blob []byte // the content of the node, nil means it's deletion
153+
blob []byte // the content of the node (nil for deletion)
154154
hash common.Hash // hash of the node content (empty for node deletion)
155155
}
156156

@@ -256,13 +256,16 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
256256
// parent for completion tracking. The given path is a unique node path in
257257
// hex format and contain all the parent path if it's layered trie node.
258258
func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) {
259-
// Short circuit if the trie is empty or already known
259+
// Short circuit if the trie is empty.
260260
if root == types.EmptyRootHash {
261261
return
262262
}
263+
// Short circuit if the trie is already known.
263264
owner, inner := ResolvePath(path)
264-
if s.hasNode(owner, inner, root) {
265+
if exist, mismatch := s.hasNode(owner, inner, root); exist {
265266
return
267+
} else if mismatch {
268+
s.membatch.delNode(owner, inner) // remove the inconsistent node.
266269
}
267270
// Assemble the new sub-trie sync request
268271
req := &nodeRequest{
@@ -424,9 +427,12 @@ func (s *Sync) Commit(dbw ethdb.Batch) error {
424427
)
425428
for _, op := range s.membatch.nodes {
426429
if op.isDelete() {
427-
// node deletion is only supported in path mode for which
428-
// node hash is not required.
429-
rawdb.DeleteTrieNode(dbw, op.owner, op.path, common.Hash{} /* unused */, s.scheme)
430+
// node deletion is not supported in path mode.
431+
if op.owner == (common.Hash{}) {
432+
rawdb.DeleteAccountTrieNode(dbw, op.path)
433+
} else {
434+
rawdb.DeleteStorageTrieNode(dbw, op.owner, op.path)
435+
}
430436
deletionGauge.Inc(1)
431437
} else {
432438
if op.owner == (common.Hash{}) {
@@ -566,6 +572,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
566572
var (
567573
missing = make(chan *nodeRequest, len(children))
568574
pending sync.WaitGroup
575+
batchMu sync.Mutex
569576
)
570577
for _, child := range children {
571578
// Notify any external watcher of a new key/value node
@@ -590,11 +597,14 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
590597
go func(path []byte, hash common.Hash) {
591598
defer pending.Done()
592599

593-
// If database says duplicate, then at least the trie node is present
594-
// and we hold the assumption that it's NOT legacy contract code.
600+
// Short circuit if the child node is already known.
595601
owner, inner := ResolvePath(path)
596-
if s.hasNode(owner, inner, hash) {
602+
if exist, mismatch := s.hasNode(owner, inner, hash); exist {
597603
return
604+
} else if mismatch {
605+
batchMu.Lock()
606+
s.membatch.delNode(owner, inner) // remove the inconsistent node
607+
batchMu.Unlock()
598608
}
599609
// Locally unknown node, schedule for retrieval
600610
missing <- &nodeRequest{
@@ -667,38 +677,27 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error {
667677
return nil
668678
}
669679

670-
// hasNode reports if the specified trie node is present in database or not.
671-
//
672-
// Notably, the existent node should be wiped in path scheme if it's not matched
673-
// with the requested one, otherwise the persistent state will end up with a
674-
// weird situation that parent node is inconsistent with children while they
675-
// are all present in database.
676-
func (s *Sync) hasNode(owner common.Hash, path []byte, root common.Hash) bool {
677-
// If node is run with hash scheme, check the presence with node hash.
680+
// hasNode reports whether the specified trie node is already present in the
681+
// database. Additionally, it returns a flag in the path-based scheme if
682+
// there is an inconsistent node existing at the specified path.
683+
func (s *Sync) hasNode(owner common.Hash, path []byte, root common.Hash) (bool, bool) {
684+
// If node is running with hash scheme, check the presence with node hash.
678685
if s.scheme == rawdb.HashScheme {
679-
return rawdb.HasLegacyTrieNode(s.database, root)
686+
return rawdb.HasLegacyTrieNode(s.database, root), false
680687
}
681-
// If node is run with path scheme, check the presence with node path.
688+
// If node is running with path scheme, check the presence with node path.
682689
if owner == (common.Hash{}) {
683690
blob, hash := rawdb.ReadAccountTrieNode(s.database, path)
684691
if hash == root {
685-
return true
686-
}
687-
// Remove the inconsistent node before expanding the path.
688-
if len(blob) != 0 {
689-
s.membatch.delNode(common.Hash{}, path)
692+
return true, false
690693
}
691-
return false
694+
return false, len(blob) != 0 // flag if the inconsistent node is present
692695
}
693696
blob, hash := rawdb.ReadStorageTrieNode(s.database, owner, path)
694697
if hash == root {
695-
return true
696-
}
697-
// Remove the inconsistent node before expanding the path.
698-
if len(blob) != 0 {
699-
s.membatch.delNode(owner, path)
698+
return true, false
700699
}
701-
return false
700+
return false, len(blob) != 0 // flag if the inconsistent node is present
702701
}
703702

704703
// ResolvePath resolves the provided composite node path by separating the

0 commit comments

Comments
 (0)