github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/nodes.go (about)

     1  // Copyright 2024 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package pathdb
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"maps"
    24  
    25  	"github.com/VictoriaMetrics/fastcache"
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core/rawdb"
    28  	"github.com/ethereum/go-ethereum/crypto"
    29  	"github.com/ethereum/go-ethereum/ethdb"
    30  	"github.com/ethereum/go-ethereum/log"
    31  	"github.com/ethereum/go-ethereum/rlp"
    32  	"github.com/ethereum/go-ethereum/trie/trienode"
    33  )
    34  
    35  // nodeSet represents a collection of modified trie nodes resulting from a state
    36  // transition, typically corresponding to a block execution. It can also represent
    37  // the combined trie node set from several aggregated state transitions.
    38  type nodeSet struct {
    39  	size         uint64                                    // aggregated size of the trie node
    40  	accountNodes map[string]*trienode.Node                 // account trie nodes, mapped by path
    41  	storageNodes map[common.Hash]map[string]*trienode.Node // storage trie nodes, mapped by owner and path
    42  }
    43  
    44  // newNodeSet constructs the set with the provided dirty trie nodes.
    45  func newNodeSet(nodes map[common.Hash]map[string]*trienode.Node) *nodeSet {
    46  	// Don't panic for the lazy callers, initialize the nil map instead
    47  	if nodes == nil {
    48  		nodes = make(map[common.Hash]map[string]*trienode.Node)
    49  	}
    50  	s := &nodeSet{
    51  		accountNodes: make(map[string]*trienode.Node),
    52  		storageNodes: make(map[common.Hash]map[string]*trienode.Node),
    53  	}
    54  	for owner, subset := range nodes {
    55  		if owner == (common.Hash{}) {
    56  			s.accountNodes = subset
    57  		} else {
    58  			s.storageNodes[owner] = subset
    59  		}
    60  	}
    61  	s.computeSize()
    62  	return s
    63  }
    64  
    65  // computeSize calculates the database size of the held trie nodes.
    66  func (s *nodeSet) computeSize() {
    67  	var size uint64
    68  	for path, n := range s.accountNodes {
    69  		size += uint64(len(n.Blob) + len(path))
    70  	}
    71  	for _, subset := range s.storageNodes {
    72  		for path, n := range subset {
    73  			size += uint64(common.HashLength + len(n.Blob) + len(path))
    74  		}
    75  	}
    76  	s.size = size
    77  }
    78  
    79  // updateSize updates the total cache size by the given delta.
    80  func (s *nodeSet) updateSize(delta int64) {
    81  	size := int64(s.size) + delta
    82  	if size >= 0 {
    83  		s.size = uint64(size)
    84  		return
    85  	}
    86  	log.Error("Nodeset size underflow", "prev", common.StorageSize(s.size), "delta", common.StorageSize(delta))
    87  	s.size = 0
    88  }
    89  
    90  // node retrieves the trie node with node path and its trie identifier.
    91  func (s *nodeSet) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
    92  	// Account trie node
    93  	if owner == (common.Hash{}) {
    94  		n, ok := s.accountNodes[string(path)]
    95  		return n, ok
    96  	}
    97  	// Storage trie node
    98  	subset, ok := s.storageNodes[owner]
    99  	if !ok {
   100  		return nil, false
   101  	}
   102  	n, ok := subset[string(path)]
   103  	return n, ok
   104  }
   105  
   106  // merge integrates the provided dirty nodes into the set. The provided nodeset
   107  // will remain unchanged, as it may still be referenced by other layers.
   108  func (s *nodeSet) merge(set *nodeSet) {
   109  	var (
   110  		delta     int64   // size difference resulting from node merging
   111  		overwrite counter // counter of nodes being overwritten
   112  	)
   113  
   114  	// Merge account nodes
   115  	for path, n := range set.accountNodes {
   116  		if orig, exist := s.accountNodes[path]; !exist {
   117  			delta += int64(len(n.Blob) + len(path))
   118  		} else {
   119  			delta += int64(len(n.Blob) - len(orig.Blob))
   120  			overwrite.add(len(orig.Blob) + len(path))
   121  		}
   122  		s.accountNodes[path] = n
   123  	}
   124  
   125  	// Merge storage nodes
   126  	for owner, subset := range set.storageNodes {
   127  		current, exist := s.storageNodes[owner]
   128  		if !exist {
   129  			for path, n := range subset {
   130  				delta += int64(common.HashLength + len(n.Blob) + len(path))
   131  			}
   132  			// Perform a shallow copy of the map for the subset instead of claiming it
   133  			// directly from the provided nodeset to avoid potential concurrent map
   134  			// read/write issues. The nodes belonging to the original diff layer remain
   135  			// accessible even after merging. Therefore, ownership of the nodes map
   136  			// should still belong to the original layer, and any modifications to it
   137  			// should be prevented.
   138  			s.storageNodes[owner] = maps.Clone(subset)
   139  			continue
   140  		}
   141  		for path, n := range subset {
   142  			if orig, exist := current[path]; !exist {
   143  				delta += int64(common.HashLength + len(n.Blob) + len(path))
   144  			} else {
   145  				delta += int64(len(n.Blob) - len(orig.Blob))
   146  				overwrite.add(common.HashLength + len(orig.Blob) + len(path))
   147  			}
   148  			current[path] = n
   149  		}
   150  		s.storageNodes[owner] = current
   151  	}
   152  	overwrite.report(gcTrieNodeMeter, gcTrieNodeBytesMeter)
   153  	s.updateSize(delta)
   154  }
   155  
   156  // revertTo merges the provided trie nodes into the set. This should reverse the
   157  // changes made by the most recent state transition.
   158  func (s *nodeSet) revertTo(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) {
   159  	var delta int64
   160  	for owner, subset := range nodes {
   161  		if owner == (common.Hash{}) {
   162  			// Account trie nodes
   163  			for path, n := range subset {
   164  				orig, ok := s.accountNodes[path]
   165  				if !ok {
   166  					blob := rawdb.ReadAccountTrieNode(db, []byte(path))
   167  					if bytes.Equal(blob, n.Blob) {
   168  						continue
   169  					}
   170  					panic(fmt.Sprintf("non-existent account node (%v) blob: %v", path, crypto.Keccak256Hash(n.Blob).Hex()))
   171  				}
   172  				s.accountNodes[path] = n
   173  				delta += int64(len(n.Blob)) - int64(len(orig.Blob))
   174  			}
   175  		} else {
   176  			// Storage trie nodes
   177  			current, ok := s.storageNodes[owner]
   178  			if !ok {
   179  				panic(fmt.Sprintf("non-existent subset (%x)", owner))
   180  			}
   181  			for path, n := range subset {
   182  				orig, ok := current[path]
   183  				if !ok {
   184  					blob := rawdb.ReadStorageTrieNode(db, owner, []byte(path))
   185  					if bytes.Equal(blob, n.Blob) {
   186  						continue
   187  					}
   188  					panic(fmt.Sprintf("non-existent storage node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
   189  				}
   190  				current[path] = n
   191  				delta += int64(len(n.Blob)) - int64(len(orig.Blob))
   192  			}
   193  		}
   194  	}
   195  	s.updateSize(delta)
   196  }
   197  
   198  // journalNode represents a trie node persisted in the journal.
   199  type journalNode struct {
   200  	Path []byte // Path of the node in the trie
   201  	Blob []byte // RLP-encoded trie node blob, nil means the node is deleted
   202  }
   203  
   204  // journalNodes represents a list trie nodes belong to a single account
   205  // or the main account trie.
   206  type journalNodes struct {
   207  	Owner common.Hash
   208  	Nodes []journalNode
   209  }
   210  
   211  // encode serializes the content of trie nodes into the provided writer.
   212  func (s *nodeSet) encode(w io.Writer) error {
   213  	nodes := make([]journalNodes, 0, len(s.storageNodes)+1)
   214  
   215  	// Encode account nodes
   216  	if len(s.accountNodes) > 0 {
   217  		entry := journalNodes{Owner: common.Hash{}}
   218  		for path, node := range s.accountNodes {
   219  			entry.Nodes = append(entry.Nodes, journalNode{
   220  				Path: []byte(path),
   221  				Blob: node.Blob,
   222  			})
   223  		}
   224  		nodes = append(nodes, entry)
   225  	}
   226  	// Encode storage nodes
   227  	for owner, subset := range s.storageNodes {
   228  		entry := journalNodes{Owner: owner}
   229  		for path, node := range subset {
   230  			entry.Nodes = append(entry.Nodes, journalNode{
   231  				Path: []byte(path),
   232  				Blob: node.Blob,
   233  			})
   234  		}
   235  		nodes = append(nodes, entry)
   236  	}
   237  	return rlp.Encode(w, nodes)
   238  }
   239  
   240  // decode deserializes the content from the rlp stream into the nodeset.
   241  func (s *nodeSet) decode(r *rlp.Stream) error {
   242  	var encoded []journalNodes
   243  	if err := r.Decode(&encoded); err != nil {
   244  		return fmt.Errorf("load nodes: %v", err)
   245  	}
   246  	s.accountNodes = make(map[string]*trienode.Node)
   247  	s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
   248  
   249  	for _, entry := range encoded {
   250  		if entry.Owner == (common.Hash{}) {
   251  			// Account nodes
   252  			for _, n := range entry.Nodes {
   253  				if len(n.Blob) > 0 {
   254  					s.accountNodes[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
   255  				} else {
   256  					s.accountNodes[string(n.Path)] = trienode.NewDeleted()
   257  				}
   258  			}
   259  		} else {
   260  			// Storage nodes
   261  			subset := make(map[string]*trienode.Node)
   262  			for _, n := range entry.Nodes {
   263  				if len(n.Blob) > 0 {
   264  					subset[string(n.Path)] = trienode.New(crypto.Keccak256Hash(n.Blob), n.Blob)
   265  				} else {
   266  					subset[string(n.Path)] = trienode.NewDeleted()
   267  				}
   268  			}
   269  			s.storageNodes[entry.Owner] = subset
   270  		}
   271  	}
   272  	s.computeSize()
   273  	return nil
   274  }
   275  
   276  // write flushes nodes into the provided database batch as a whole.
   277  func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int {
   278  	nodes := make(map[common.Hash]map[string]*trienode.Node)
   279  	if len(s.accountNodes) > 0 {
   280  		nodes[common.Hash{}] = s.accountNodes
   281  	}
   282  	for owner, subset := range s.storageNodes {
   283  		nodes[owner] = subset
   284  	}
   285  	return writeNodes(batch, nodes, clean)
   286  }
   287  
   288  // reset clears all cached trie node data.
   289  func (s *nodeSet) reset() {
   290  	s.accountNodes = make(map[string]*trienode.Node)
   291  	s.storageNodes = make(map[common.Hash]map[string]*trienode.Node)
   292  	s.size = 0
   293  }
   294  
   295  // dbsize returns the approximate size of db write.
   296  func (s *nodeSet) dbsize() int {
   297  	var m int
   298  	m += len(s.accountNodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
   299  	for _, nodes := range s.storageNodes {
   300  		m += len(nodes) * (len(rawdb.TrieNodeStoragePrefix)) // database key prefix
   301  	}
   302  	return m + int(s.size)
   303  }