github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/triedb/pathdb/nodebuffer.go (about)

     1  // Copyright 2022 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  	"time"
    23  
    24  	"github.com/VictoriaMetrics/fastcache"
    25  	"github.com/ethereum/go-ethereum/common"
    26  	"github.com/ethereum/go-ethereum/core/rawdb"
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/ethdb"
    29  	"github.com/ethereum/go-ethereum/log"
    30  	"github.com/ethereum/go-ethereum/trie/trienode"
    31  )
    32  
    33  // nodebuffer is a collection of modified trie nodes to aggregate the disk
    34  // write. The content of the nodebuffer must be checked before diving into
    35  // disk (since it basically is not-yet-written data).
    36  type nodebuffer struct {
    37  	layers uint64                                    // The number of diff layers aggregated inside
    38  	size   uint64                                    // The size of aggregated writes
    39  	limit  uint64                                    // The maximum memory allowance in bytes
    40  	nodes  map[common.Hash]map[string]*trienode.Node // The dirty node set, mapped by owner and path
    41  }
    42  
    43  // newNodeBuffer initializes the node buffer with the provided nodes.
    44  func newNodeBuffer(limit int, nodes map[common.Hash]map[string]*trienode.Node, layers uint64) *nodebuffer {
    45  	if nodes == nil {
    46  		nodes = make(map[common.Hash]map[string]*trienode.Node)
    47  	}
    48  	var size uint64
    49  	for _, subset := range nodes {
    50  		for path, n := range subset {
    51  			size += uint64(len(n.Blob) + len(path))
    52  		}
    53  	}
    54  	return &nodebuffer{
    55  		layers: layers,
    56  		nodes:  nodes,
    57  		size:   size,
    58  		limit:  uint64(limit),
    59  	}
    60  }
    61  
    62  // node retrieves the trie node with given node info.
    63  func (b *nodebuffer) node(owner common.Hash, path []byte) (*trienode.Node, bool) {
    64  	subset, ok := b.nodes[owner]
    65  	if !ok {
    66  		return nil, false
    67  	}
    68  	n, ok := subset[string(path)]
    69  	if !ok {
    70  		return nil, false
    71  	}
    72  	return n, true
    73  }
    74  
    75  // commit merges the dirty nodes into the nodebuffer. This operation won't take
    76  // the ownership of the nodes map which belongs to the bottom-most diff layer.
    77  // It will just hold the node references from the given map which are safe to
    78  // copy.
    79  func (b *nodebuffer) commit(nodes map[common.Hash]map[string]*trienode.Node) *nodebuffer {
    80  	var (
    81  		delta         int64
    82  		overwrite     int64
    83  		overwriteSize int64
    84  	)
    85  	for owner, subset := range nodes {
    86  		current, exist := b.nodes[owner]
    87  		if !exist {
    88  			// Allocate a new map for the subset instead of claiming it directly
    89  			// from the passed map to avoid potential concurrent map read/write.
    90  			// The nodes belong to original diff layer are still accessible even
    91  			// after merging, thus the ownership of nodes map should still belong
    92  			// to original layer and any mutation on it should be prevented.
    93  			current = make(map[string]*trienode.Node, len(subset))
    94  			for path, n := range subset {
    95  				current[path] = n
    96  				delta += int64(len(n.Blob) + len(path))
    97  			}
    98  			b.nodes[owner] = current
    99  			continue
   100  		}
   101  		for path, n := range subset {
   102  			if orig, exist := current[path]; !exist {
   103  				delta += int64(len(n.Blob) + len(path))
   104  			} else {
   105  				delta += int64(len(n.Blob) - len(orig.Blob))
   106  				overwrite++
   107  				overwriteSize += int64(len(orig.Blob) + len(path))
   108  			}
   109  			current[path] = n
   110  		}
   111  		b.nodes[owner] = current
   112  	}
   113  	b.updateSize(delta)
   114  	b.layers++
   115  	gcNodesMeter.Mark(overwrite)
   116  	gcBytesMeter.Mark(overwriteSize)
   117  	return b
   118  }
   119  
   120  // revert is the reverse operation of commit. It also merges the provided nodes
   121  // into the nodebuffer, the difference is that the provided node set should
   122  // revert the changes made by the last state transition.
   123  func (b *nodebuffer) revert(db ethdb.KeyValueReader, nodes map[common.Hash]map[string]*trienode.Node) error {
   124  	// Short circuit if no embedded state transition to revert.
   125  	if b.layers == 0 {
   126  		return errStateUnrecoverable
   127  	}
   128  	b.layers--
   129  
   130  	// Reset the entire buffer if only a single transition left.
   131  	if b.layers == 0 {
   132  		b.reset()
   133  		return nil
   134  	}
   135  	var delta int64
   136  	for owner, subset := range nodes {
   137  		current, ok := b.nodes[owner]
   138  		if !ok {
   139  			panic(fmt.Sprintf("non-existent subset (%x)", owner))
   140  		}
   141  		for path, n := range subset {
   142  			orig, ok := current[path]
   143  			if !ok {
   144  				// There is a special case in MPT that one child is removed from
   145  				// a fullNode which only has two children, and then a new child
   146  				// with different position is immediately inserted into the fullNode.
   147  				// In this case, the clean child of the fullNode will also be
   148  				// marked as dirty because of node collapse and expansion.
   149  				//
   150  				// In case of database rollback, don't panic if this "clean"
   151  				// node occurs which is not present in buffer.
   152  				var blob []byte
   153  				if owner == (common.Hash{}) {
   154  					blob = rawdb.ReadAccountTrieNode(db, []byte(path))
   155  				} else {
   156  					blob = rawdb.ReadStorageTrieNode(db, owner, []byte(path))
   157  				}
   158  				// Ignore the clean node in the case described above.
   159  				if bytes.Equal(blob, n.Blob) {
   160  					continue
   161  				}
   162  				panic(fmt.Sprintf("non-existent node (%x %v) blob: %v", owner, path, crypto.Keccak256Hash(n.Blob).Hex()))
   163  			}
   164  			current[path] = n
   165  			delta += int64(len(n.Blob)) - int64(len(orig.Blob))
   166  		}
   167  	}
   168  	b.updateSize(delta)
   169  	return nil
   170  }
   171  
   172  // updateSize updates the total cache size by the given delta.
   173  func (b *nodebuffer) updateSize(delta int64) {
   174  	size := int64(b.size) + delta
   175  	if size >= 0 {
   176  		b.size = uint64(size)
   177  		return
   178  	}
   179  	s := b.size
   180  	b.size = 0
   181  	log.Error("Invalid pathdb buffer size", "prev", common.StorageSize(s), "delta", common.StorageSize(delta))
   182  }
   183  
   184  // reset cleans up the disk cache.
   185  func (b *nodebuffer) reset() {
   186  	b.layers = 0
   187  	b.size = 0
   188  	b.nodes = make(map[common.Hash]map[string]*trienode.Node)
   189  }
   190  
   191  // empty returns an indicator if nodebuffer contains any state transition inside.
   192  func (b *nodebuffer) empty() bool {
   193  	return b.layers == 0
   194  }
   195  
   196  // setSize sets the buffer size to the provided number, and invokes a flush
   197  // operation if the current memory usage exceeds the new limit.
   198  func (b *nodebuffer) setSize(size int, db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64) error {
   199  	b.limit = uint64(size)
   200  	return b.flush(db, clean, id, false)
   201  }
   202  
   203  // allocBatch returns a database batch with pre-allocated buffer.
   204  func (b *nodebuffer) allocBatch(db ethdb.KeyValueStore) ethdb.Batch {
   205  	var metasize int
   206  	for owner, nodes := range b.nodes {
   207  		if owner == (common.Hash{}) {
   208  			metasize += len(nodes) * len(rawdb.TrieNodeAccountPrefix) // database key prefix
   209  		} else {
   210  			metasize += len(nodes) * (len(rawdb.TrieNodeStoragePrefix) + common.HashLength) // database key prefix + owner
   211  		}
   212  	}
   213  	return db.NewBatchWithSize((metasize + int(b.size)) * 11 / 10) // extra 10% for potential pebble internal stuff
   214  }
   215  
   216  // flush persists the in-memory dirty trie node into the disk if the configured
   217  // memory threshold is reached. Note, all data must be written atomically.
   218  func (b *nodebuffer) flush(db ethdb.KeyValueStore, clean *fastcache.Cache, id uint64, force bool) error {
   219  	if b.size <= b.limit && !force {
   220  		return nil
   221  	}
   222  	// Ensure the target state id is aligned with the internal counter.
   223  	head := rawdb.ReadPersistentStateID(db)
   224  	if head+b.layers != id {
   225  		return fmt.Errorf("buffer layers (%d) cannot be applied on top of persisted state id (%d) to reach requested state id (%d)", b.layers, head, id)
   226  	}
   227  	var (
   228  		start = time.Now()
   229  		batch = b.allocBatch(db)
   230  	)
   231  	nodes := writeNodes(batch, b.nodes, clean)
   232  	rawdb.WritePersistentStateID(batch, id)
   233  
   234  	// Flush all mutations in a single batch
   235  	size := batch.ValueSize()
   236  	if err := batch.Write(); err != nil {
   237  		return err
   238  	}
   239  	commitBytesMeter.Mark(int64(size))
   240  	commitNodesMeter.Mark(int64(nodes))
   241  	commitTimeTimer.UpdateSince(start)
   242  	log.Debug("Persisted pathdb nodes", "nodes", len(b.nodes), "bytes", common.StorageSize(size), "elapsed", common.PrettyDuration(time.Since(start)))
   243  	b.reset()
   244  	return nil
   245  }
   246  
   247  // writeNodes writes the trie nodes into the provided database batch.
   248  // Note this function will also inject all the newly written nodes
   249  // into clean cache.
   250  func writeNodes(batch ethdb.Batch, nodes map[common.Hash]map[string]*trienode.Node, clean *fastcache.Cache) (total int) {
   251  	for owner, subset := range nodes {
   252  		for path, n := range subset {
   253  			if n.IsDeleted() {
   254  				if owner == (common.Hash{}) {
   255  					rawdb.DeleteAccountTrieNode(batch, []byte(path))
   256  				} else {
   257  					rawdb.DeleteStorageTrieNode(batch, owner, []byte(path))
   258  				}
   259  				if clean != nil {
   260  					clean.Del(cacheKey(owner, []byte(path)))
   261  				}
   262  			} else {
   263  				if owner == (common.Hash{}) {
   264  					rawdb.WriteAccountTrieNode(batch, []byte(path), n.Blob)
   265  				} else {
   266  					rawdb.WriteStorageTrieNode(batch, owner, []byte(path), n.Blob)
   267  				}
   268  				if clean != nil {
   269  					clean.Set(cacheKey(owner, []byte(path)), n.Blob)
   270  				}
   271  			}
   272  		}
   273  		total += len(subset)
   274  	}
   275  	return total
   276  }
   277  
   278  // cacheKey constructs the unique key of clean cache.
   279  func cacheKey(owner common.Hash, path []byte) []byte {
   280  	if owner == (common.Hash{}) {
   281  		return path
   282  	}
   283  	return append(owner.Bytes(), path...)
   284  }