mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
trie: fix data race around node deletion
This commit is contained in:
parent
e1bb804015
commit
fb387c98e1
1 changed files with 30 additions and 31 deletions
61
trie/sync.go
61
trie/sync.go
|
|
@ -150,7 +150,7 @@ type CodeSyncResult struct {
|
||||||
type nodeOp struct {
|
type nodeOp struct {
|
||||||
owner common.Hash // identifier of the trie (empty for account trie)
|
owner common.Hash // identifier of the trie (empty for account trie)
|
||||||
path []byte // path from the root to the specified node.
|
path []byte // path from the root to the specified node.
|
||||||
blob []byte // the content of the node, nil means it's deletion
|
blob []byte // the content of the node (nil for deletion)
|
||||||
hash common.Hash // hash of the node content (empty for node deletion)
|
hash common.Hash // hash of the node content (empty for node deletion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,13 +256,16 @@ func NewSync(root common.Hash, database ethdb.KeyValueReader, callback LeafCallb
|
||||||
// parent for completion tracking. The given path is a unique node path in
|
// parent for completion tracking. The given path is a unique node path in
|
||||||
// hex format and contain all the parent path if it's layered trie node.
|
// hex format and contain all the parent path if it's layered trie node.
|
||||||
func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) {
|
func (s *Sync) AddSubTrie(root common.Hash, path []byte, parent common.Hash, parentPath []byte, callback LeafCallback) {
|
||||||
// Short circuit if the trie is empty or already known
|
// Short circuit if the trie is empty.
|
||||||
if root == types.EmptyRootHash {
|
if root == types.EmptyRootHash {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Short circuit if the trie is already known.
|
||||||
owner, inner := ResolvePath(path)
|
owner, inner := ResolvePath(path)
|
||||||
if s.hasNode(owner, inner, root) {
|
if exist, mismatch := s.hasNode(owner, inner, root); exist {
|
||||||
return
|
return
|
||||||
|
} else if mismatch {
|
||||||
|
s.membatch.delNode(owner, inner) // remove the inconsistent node.
|
||||||
}
|
}
|
||||||
// Assemble the new sub-trie sync request
|
// Assemble the new sub-trie sync request
|
||||||
req := &nodeRequest{
|
req := &nodeRequest{
|
||||||
|
|
@ -424,9 +427,12 @@ func (s *Sync) Commit(dbw ethdb.Batch) error {
|
||||||
)
|
)
|
||||||
for _, op := range s.membatch.nodes {
|
for _, op := range s.membatch.nodes {
|
||||||
if op.isDelete() {
|
if op.isDelete() {
|
||||||
// node deletion is only supported in path mode for which
|
// node deletion is not supported in path mode.
|
||||||
// node hash is not required.
|
if op.owner == (common.Hash{}) {
|
||||||
rawdb.DeleteTrieNode(dbw, op.owner, op.path, common.Hash{} /* unused */, s.scheme)
|
rawdb.DeleteAccountTrieNode(dbw, op.path)
|
||||||
|
} else {
|
||||||
|
rawdb.DeleteStorageTrieNode(dbw, op.owner, op.path)
|
||||||
|
}
|
||||||
deletionGauge.Inc(1)
|
deletionGauge.Inc(1)
|
||||||
} else {
|
} else {
|
||||||
if op.owner == (common.Hash{}) {
|
if op.owner == (common.Hash{}) {
|
||||||
|
|
@ -566,6 +572,7 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
|
||||||
var (
|
var (
|
||||||
missing = make(chan *nodeRequest, len(children))
|
missing = make(chan *nodeRequest, len(children))
|
||||||
pending sync.WaitGroup
|
pending sync.WaitGroup
|
||||||
|
batchMu sync.Mutex
|
||||||
)
|
)
|
||||||
for _, child := range children {
|
for _, child := range children {
|
||||||
// Notify any external watcher of a new key/value node
|
// Notify any external watcher of a new key/value node
|
||||||
|
|
@ -590,11 +597,14 @@ func (s *Sync) children(req *nodeRequest, object node) ([]*nodeRequest, error) {
|
||||||
go func(path []byte, hash common.Hash) {
|
go func(path []byte, hash common.Hash) {
|
||||||
defer pending.Done()
|
defer pending.Done()
|
||||||
|
|
||||||
// If database says duplicate, then at least the trie node is present
|
// Short circuit if the child node is already known.
|
||||||
// and we hold the assumption that it's NOT legacy contract code.
|
|
||||||
owner, inner := ResolvePath(path)
|
owner, inner := ResolvePath(path)
|
||||||
if s.hasNode(owner, inner, hash) {
|
if exist, mismatch := s.hasNode(owner, inner, hash); exist {
|
||||||
return
|
return
|
||||||
|
} else if mismatch {
|
||||||
|
batchMu.Lock()
|
||||||
|
s.membatch.delNode(owner, inner) // remove the inconsistent node
|
||||||
|
batchMu.Unlock()
|
||||||
}
|
}
|
||||||
// Locally unknown node, schedule for retrieval
|
// Locally unknown node, schedule for retrieval
|
||||||
missing <- &nodeRequest{
|
missing <- &nodeRequest{
|
||||||
|
|
@ -667,38 +677,27 @@ func (s *Sync) commitCodeRequest(req *codeRequest) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasNode reports if the specified trie node is present in database or not.
|
// hasNode reports whether the specified trie node is already present in the
|
||||||
//
|
// database. Additionally, it returns a flag in the path-based scheme if
|
||||||
// Notably, the existent node should be wiped in path scheme if it's not matched
|
// there is an inconsistent node existing at the specified path.
|
||||||
// with the requested one, otherwise the persistent state will end up with a
|
func (s *Sync) hasNode(owner common.Hash, path []byte, root common.Hash) (bool, bool) {
|
||||||
// weird situation that parent node is inconsistent with children while they
|
// If node is running with hash scheme, check the presence with node hash.
|
||||||
// are all present in database.
|
|
||||||
func (s *Sync) hasNode(owner common.Hash, path []byte, root common.Hash) bool {
|
|
||||||
// If node is run with hash scheme, check the presence with node hash.
|
|
||||||
if s.scheme == rawdb.HashScheme {
|
if s.scheme == rawdb.HashScheme {
|
||||||
return rawdb.HasLegacyTrieNode(s.database, root)
|
return rawdb.HasLegacyTrieNode(s.database, root), false
|
||||||
}
|
}
|
||||||
// If node is run with path scheme, check the presence with node path.
|
// If node is running with path scheme, check the presence with node path.
|
||||||
if owner == (common.Hash{}) {
|
if owner == (common.Hash{}) {
|
||||||
blob, hash := rawdb.ReadAccountTrieNode(s.database, path)
|
blob, hash := rawdb.ReadAccountTrieNode(s.database, path)
|
||||||
if hash == root {
|
if hash == root {
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
// Remove the inconsistent node before expanding the path.
|
return false, len(blob) != 0 // flag if the inconsistent node is present
|
||||||
if len(blob) != 0 {
|
|
||||||
s.membatch.delNode(common.Hash{}, path)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
blob, hash := rawdb.ReadStorageTrieNode(s.database, owner, path)
|
blob, hash := rawdb.ReadStorageTrieNode(s.database, owner, path)
|
||||||
if hash == root {
|
if hash == root {
|
||||||
return true
|
return true, false
|
||||||
}
|
}
|
||||||
// Remove the inconsistent node before expanding the path.
|
return false, len(blob) != 0 // flag if the inconsistent node is present
|
||||||
if len(blob) != 0 {
|
|
||||||
s.membatch.delNode(owner, path)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolvePath resolves the provided composite node path by separating the
|
// ResolvePath resolves the provided composite node path by separating the
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue