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