github.com/ethereum/go-ethereum@v1.16.1/triedb/pathdb/lookup.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  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/ethereum/go-ethereum/common"
    25  	"golang.org/x/sync/errgroup"
    26  )
    27  
    28  // storageKey returns a key for uniquely identifying the storage slot.
    29  func storageKey(accountHash common.Hash, slotHash common.Hash) [64]byte {
    30  	var key [64]byte
    31  	copy(key[:32], accountHash[:])
    32  	copy(key[32:], slotHash[:])
    33  	return key
    34  }
    35  
    36  // lookup is an internal structure used to efficiently determine the layer in
    37  // which a state entry resides.
    38  type lookup struct {
    39  	// accounts represents the mutation history for specific accounts.
    40  	// The key is the account address hash, and the value is a slice
    41  	// of **diff layer** IDs indicating where the account was modified,
    42  	// with the order from oldest to newest.
    43  	accounts map[common.Hash][]common.Hash
    44  
    45  	// storages represents the mutation history for specific storage
    46  	// slot. The key is the account address hash and the storage key
    47  	// hash, the value is a slice of **diff layer** IDs indicating
    48  	// where the slot was modified, with the order from oldest to newest.
    49  	storages map[[64]byte][]common.Hash
    50  
    51  	// descendant is the callback indicating whether the layer with
    52  	// given root is a descendant of the one specified by `ancestor`.
    53  	descendant func(state common.Hash, ancestor common.Hash) bool
    54  }
    55  
    56  // newLookup initializes the lookup structure.
    57  func newLookup(head layer, descendant func(state common.Hash, ancestor common.Hash) bool) *lookup {
    58  	var (
    59  		current = head
    60  		layers  []layer
    61  	)
    62  	for current != nil {
    63  		layers = append(layers, current)
    64  		current = current.parentLayer()
    65  	}
    66  	l := &lookup{
    67  		accounts:   make(map[common.Hash][]common.Hash),
    68  		storages:   make(map[[64]byte][]common.Hash),
    69  		descendant: descendant,
    70  	}
    71  	// Apply the diff layers from bottom to top
    72  	for i := len(layers) - 1; i >= 0; i-- {
    73  		switch diff := layers[i].(type) {
    74  		case *diskLayer:
    75  			continue
    76  		case *diffLayer:
    77  			l.addLayer(diff)
    78  		}
    79  	}
    80  	return l
    81  }
    82  
    83  // accountTip traverses the layer list associated with the given account in
    84  // reverse order to locate the first entry that either matches the specified
    85  // stateID or is a descendant of it.
    86  //
    87  // If found, the account data corresponding to the supplied stateID resides
    88  // in that layer. Otherwise, two scenarios are possible:
    89  //
    90  // (a) the account remains unmodified from the current disk layer up to the state
    91  // layer specified by the stateID: fallback to the disk layer for data retrieval,
    92  // (b) or the layer specified by the stateID is stale: reject the data retrieval.
    93  func (l *lookup) accountTip(accountHash common.Hash, stateID common.Hash, base common.Hash) common.Hash {
    94  	// Traverse the mutation history from latest to oldest one. Several
    95  	// scenarios are possible:
    96  	//
    97  	// Chain:
    98  	//     D->C1->C2->C3->C4 (HEAD)
    99  	//      ->C1'->C2'->C3'
   100  	// State:
   101  	//     x: [C1, C1', C3', C3]
   102  	//     y: []
   103  	//
   104  	// - (x, C4) => C3
   105  	// - (x, C3) => C3
   106  	// - (x, C2) => C1
   107  	// - (x, C3') => C3'
   108  	// - (x, C2') => C1'
   109  	// - (y, C4) => D
   110  	// - (y, C3') => D
   111  	// - (y, C0) => null
   112  	list := l.accounts[accountHash]
   113  	for i := len(list) - 1; i >= 0; i-- {
   114  		// If the current state matches the stateID, or the requested state is a
   115  		// descendant of it, return the current state as the most recent one
   116  		// containing the modified data. Otherwise, the current state may be ahead
   117  		// of the requested one or belong to a different branch.
   118  		if list[i] == stateID || l.descendant(stateID, list[i]) {
   119  			return list[i]
   120  		}
   121  	}
   122  	// No layer matching the stateID or its descendants was found. Use the
   123  	// current disk layer as a fallback.
   124  	if base == stateID || l.descendant(stateID, base) {
   125  		return base
   126  	}
   127  	// The layer associated with 'stateID' is not the descendant of the current
   128  	// disk layer, it's already stale, return nothing.
   129  	return common.Hash{}
   130  }
   131  
   132  // storageTip traverses the layer list associated with the given account and
   133  // slot hash in reverse order to locate the first entry that either matches
   134  // the specified stateID or is a descendant of it.
   135  //
   136  // If found, the storage data corresponding to the supplied stateID resides
   137  // in that layer. Otherwise, two scenarios are possible:
   138  //
   139  // (a) the storage slot remains unmodified from the current disk layer up to
   140  // the state layer specified by the stateID: fallback to the disk layer for
   141  // data retrieval, (b) or the layer specified by the stateID is stale: reject
   142  // the data retrieval.
   143  func (l *lookup) storageTip(accountHash common.Hash, slotHash common.Hash, stateID common.Hash, base common.Hash) common.Hash {
   144  	list := l.storages[storageKey(accountHash, slotHash)]
   145  	for i := len(list) - 1; i >= 0; i-- {
   146  		// If the current state matches the stateID, or the requested state is a
   147  		// descendant of it, return the current state as the most recent one
   148  		// containing the modified data. Otherwise, the current state may be ahead
   149  		// of the requested one or belong to a different branch.
   150  		if list[i] == stateID || l.descendant(stateID, list[i]) {
   151  			return list[i]
   152  		}
   153  	}
   154  	// No layer matching the stateID or its descendants was found. Use the
   155  	// current disk layer as a fallback.
   156  	if base == stateID || l.descendant(stateID, base) {
   157  		return base
   158  	}
   159  	// The layer associated with 'stateID' is not the descendant of the current
   160  	// disk layer, it's already stale, return nothing.
   161  	return common.Hash{}
   162  }
   163  
   164  // addLayer traverses the state data retained in the specified diff layer and
   165  // integrates it into the lookup set.
   166  //
   167  // This function assumes that all layers older than the provided one have already
   168  // been processed, ensuring that layers are processed strictly in a bottom-to-top
   169  // order.
   170  func (l *lookup) addLayer(diff *diffLayer) {
   171  	defer func(now time.Time) {
   172  		lookupAddLayerTimer.UpdateSince(now)
   173  	}(time.Now())
   174  
   175  	var (
   176  		wg    sync.WaitGroup
   177  		state = diff.rootHash()
   178  	)
   179  	wg.Add(1)
   180  	go func() {
   181  		defer wg.Done()
   182  		for accountHash := range diff.states.accountData {
   183  			list, exists := l.accounts[accountHash]
   184  			if !exists {
   185  				list = make([]common.Hash, 0, 16) // TODO(rjl493456442) use sync pool
   186  			}
   187  			list = append(list, state)
   188  			l.accounts[accountHash] = list
   189  		}
   190  	}()
   191  
   192  	wg.Add(1)
   193  	go func() {
   194  		defer wg.Done()
   195  		for accountHash, slots := range diff.states.storageData {
   196  			for slotHash := range slots {
   197  				key := storageKey(accountHash, slotHash)
   198  				list, exists := l.storages[key]
   199  				if !exists {
   200  					list = make([]common.Hash, 0, 16) // TODO(rjl493456442) use sync pool
   201  				}
   202  				list = append(list, state)
   203  				l.storages[key] = list
   204  			}
   205  		}
   206  	}()
   207  	wg.Wait()
   208  }
   209  
   210  // removeFromList removes the specified element from the provided list.
   211  // It returns a flag indicating whether the element was found and removed.
   212  func removeFromList(list []common.Hash, element common.Hash) (bool, []common.Hash) {
   213  	// Traverse the list from oldest to newest to quickly locate the element.
   214  	for i := 0; i < len(list); i++ {
   215  		if list[i] == element {
   216  			if i != 0 {
   217  				list = append(list[:i], list[i+1:]...)
   218  			} else {
   219  				// Remove the first element by shifting the slice forward.
   220  				// Pros: zero-copy.
   221  				// Cons: may retain large backing array, causing memory leaks.
   222  				// Mitigation: release the array if capacity exceeds threshold.
   223  				list = list[1:]
   224  				if cap(list) > 1024 {
   225  					list = append(make([]common.Hash, 0, len(list)), list...)
   226  				}
   227  			}
   228  			return true, list
   229  		}
   230  	}
   231  	return false, nil
   232  }
   233  
   234  // removeLayer traverses the state data retained in the specified diff layer and
   235  // unlink them from the lookup set.
   236  func (l *lookup) removeLayer(diff *diffLayer) error {
   237  	defer func(now time.Time) {
   238  		lookupRemoveLayerTimer.UpdateSince(now)
   239  	}(time.Now())
   240  
   241  	var (
   242  		eg    errgroup.Group
   243  		state = diff.rootHash()
   244  	)
   245  	eg.Go(func() error {
   246  		for accountHash := range diff.states.accountData {
   247  			found, list := removeFromList(l.accounts[accountHash], state)
   248  			if !found {
   249  				return fmt.Errorf("account lookup is not found, %x, state: %x", accountHash, state)
   250  			}
   251  			if len(list) != 0 {
   252  				l.accounts[accountHash] = list
   253  			} else {
   254  				delete(l.accounts, accountHash)
   255  			}
   256  		}
   257  		return nil
   258  	})
   259  
   260  	eg.Go(func() error {
   261  		for accountHash, slots := range diff.states.storageData {
   262  			for slotHash := range slots {
   263  				key := storageKey(accountHash, slotHash)
   264  				found, list := removeFromList(l.storages[key], state)
   265  				if !found {
   266  					return fmt.Errorf("storage lookup is not found, %x %x, state: %x", accountHash, slotHash, state)
   267  				}
   268  				if len(list) != 0 {
   269  					l.storages[key] = list
   270  				} else {
   271  					delete(l.storages, key)
   272  				}
   273  			}
   274  		}
   275  		return nil
   276  	})
   277  	return eg.Wait()
   278  }