github.com/MetalBlockchain/metalgo@v1.11.9/x/merkledb/history.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package merkledb
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"github.com/MetalBlockchain/metalgo/ids"
    12  	"github.com/MetalBlockchain/metalgo/utils"
    13  	"github.com/MetalBlockchain/metalgo/utils/buffer"
    14  	"github.com/MetalBlockchain/metalgo/utils/maybe"
    15  	"github.com/MetalBlockchain/metalgo/utils/set"
    16  )
    17  
    18  var (
    19  	ErrInsufficientHistory = errors.New("insufficient history to generate proof")
    20  	ErrNoEndRoot           = fmt.Errorf("%w: end root not found", ErrInsufficientHistory)
    21  )
    22  
    23  // stores previous trie states
    24  type trieHistory struct {
    25  	// Root ID --> The most recent change resulting in [rootID].
    26  	lastChanges map[ids.ID]*changeSummaryAndInsertNumber
    27  
    28  	// Maximum number of previous roots/changes to store in [history].
    29  	maxHistoryLen int
    30  
    31  	// Contains the history.
    32  	// Sorted by increasing order of insertion.
    33  	// Contains at most [maxHistoryLen] values.
    34  	history buffer.Deque[*changeSummaryAndInsertNumber]
    35  
    36  	// Each change is tagged with this monotonic increasing number.
    37  	nextInsertNumber uint64
    38  }
    39  
    40  // Tracks the beginning and ending state of a value.
    41  type change[T any] struct {
    42  	before T
    43  	after  T
    44  }
    45  
    46  // Wrapper around a changeSummary that allows comparison
    47  // of when the change was made.
    48  type changeSummaryAndInsertNumber struct {
    49  	*changeSummary
    50  	// Another changeSummaryAndInsertNumber with a greater
    51  	// [insertNumber] means that change was after this one.
    52  	insertNumber uint64
    53  }
    54  
    55  // Tracks all the node and value changes that resulted in the rootID.
    56  type changeSummary struct {
    57  	// The ID of the trie after these changes.
    58  	rootID ids.ID
    59  	// The root before/after this change.
    60  	// Set in [applyValueChanges].
    61  	rootChange change[maybe.Maybe[*node]]
    62  	nodes      map[Key]*change[*node]
    63  	values     map[Key]*change[maybe.Maybe[[]byte]]
    64  }
    65  
    66  func newChangeSummary(estimatedSize int) *changeSummary {
    67  	return &changeSummary{
    68  		nodes:      make(map[Key]*change[*node], estimatedSize),
    69  		values:     make(map[Key]*change[maybe.Maybe[[]byte]], estimatedSize),
    70  		rootChange: change[maybe.Maybe[*node]]{},
    71  	}
    72  }
    73  
    74  func newTrieHistory(maxHistoryLookback int) *trieHistory {
    75  	return &trieHistory{
    76  		maxHistoryLen: maxHistoryLookback,
    77  		history:       buffer.NewUnboundedDeque[*changeSummaryAndInsertNumber](maxHistoryLookback),
    78  		lastChanges:   make(map[ids.ID]*changeSummaryAndInsertNumber),
    79  	}
    80  }
    81  
    82  // Returns up to [maxLength] key-value pair changes with keys in
    83  // [start, end] that occurred between [startRoot] and [endRoot].
    84  // If [start] is Nothing, there's no lower bound on the range.
    85  // If [end] is Nothing, there's no upper bound on the range.
    86  // Returns [ErrInsufficientHistory] if the history is insufficient
    87  // to generate the proof.
    88  // Returns [ErrNoEndRoot], which wraps [ErrInsufficientHistory], if
    89  // the [endRoot] isn't in the history.
    90  func (th *trieHistory) getValueChanges(
    91  	startRoot ids.ID,
    92  	endRoot ids.ID,
    93  	start maybe.Maybe[[]byte],
    94  	end maybe.Maybe[[]byte],
    95  	maxLength int,
    96  ) (*changeSummary, error) {
    97  	if maxLength <= 0 {
    98  		return nil, fmt.Errorf("%w but was %d", ErrInvalidMaxLength, maxLength)
    99  	}
   100  
   101  	if startRoot == endRoot {
   102  		return newChangeSummary(maxLength), nil
   103  	}
   104  
   105  	// [endRootChanges] is the last change in the history resulting in [endRoot].
   106  	endRootChanges, ok := th.lastChanges[endRoot]
   107  	if !ok {
   108  		return nil, fmt.Errorf("%w: %s", ErrNoEndRoot, endRoot)
   109  	}
   110  
   111  	// Confirm there's a change resulting in [startRoot] before
   112  	// a change resulting in [endRoot] in the history.
   113  	// [startRootChanges] is the last appearance of [startRoot].
   114  	startRootChanges, ok := th.lastChanges[startRoot]
   115  	if !ok {
   116  		return nil, fmt.Errorf("%w: start root %s not found", ErrInsufficientHistory, startRoot)
   117  	}
   118  
   119  	var (
   120  		// The insert number of the last element in [th.history].
   121  		mostRecentChangeInsertNumber = th.nextInsertNumber - 1
   122  
   123  		// The index within [th.history] of its last element.
   124  		mostRecentChangeIndex = th.history.Len() - 1
   125  
   126  		// The difference between the last index in [th.history] and the index of [endRootChanges].
   127  		endToMostRecentOffset = int(mostRecentChangeInsertNumber - endRootChanges.insertNumber)
   128  
   129  		// The index in [th.history] of the latest change resulting in [endRoot].
   130  		endRootIndex = mostRecentChangeIndex - endToMostRecentOffset
   131  	)
   132  
   133  	if startRootChanges.insertNumber > endRootChanges.insertNumber {
   134  		// [startRootChanges] happened after [endRootChanges].
   135  		// However, that is just the *latest* change resulting in [startRoot].
   136  		// Attempt to find a change resulting in [startRoot] before [endRootChanges].
   137  		//
   138  		// Translate the insert number to the index in [th.history] so we can iterate
   139  		// backward from [endRootChanges].
   140  		for i := endRootIndex - 1; i >= 0; i-- {
   141  			changes, _ := th.history.Index(i)
   142  
   143  			if changes.rootID == startRoot {
   144  				// [startRootChanges] is now the last change resulting in
   145  				// [startRoot] before [endRootChanges].
   146  				startRootChanges = changes
   147  				break
   148  			}
   149  
   150  			if i == 0 {
   151  				return nil, fmt.Errorf(
   152  					"%w: start root %s not found before end root %s",
   153  					ErrInsufficientHistory, startRoot, endRoot,
   154  				)
   155  			}
   156  		}
   157  	}
   158  
   159  	var (
   160  		// Keep track of changed keys so the largest can be removed
   161  		// in order to stay within the [maxLength] limit if necessary.
   162  		changedKeys = set.Set[Key]{}
   163  
   164  		startKey = maybe.Bind(start, ToKey)
   165  		endKey   = maybe.Bind(end, ToKey)
   166  
   167  		// For each element in the history in the range between [startRoot]'s
   168  		// last appearance (exclusive) and [endRoot]'s last appearance (inclusive),
   169  		// add the changes to keys in [start, end] to [combinedChanges].
   170  		// Only the key-value pairs with the greatest [maxLength] keys will be kept.
   171  		combinedChanges = newChangeSummary(maxLength)
   172  
   173  		// The difference between the index of [startRootChanges] and [endRootChanges] in [th.history].
   174  		startToEndOffset = int(endRootChanges.insertNumber - startRootChanges.insertNumber)
   175  
   176  		// The index of the last change resulting in [startRoot]
   177  		// which occurs before [endRootChanges].
   178  		startRootIndex = endRootIndex - startToEndOffset
   179  	)
   180  
   181  	// For each change after [startRootChanges] up to and including
   182  	// [endRootChanges], record the change in [combinedChanges].
   183  	for i := startRootIndex + 1; i <= endRootIndex; i++ {
   184  		changes, _ := th.history.Index(i)
   185  
   186  		// Add the changes from this commit to [combinedChanges].
   187  		for key, valueChange := range changes.values {
   188  			// The key is outside the range [start, end].
   189  			if (startKey.HasValue() && key.Less(startKey.Value())) ||
   190  				(end.HasValue() && key.Greater(endKey.Value())) {
   191  				continue
   192  			}
   193  
   194  			// A change to this key already exists in [combinedChanges]
   195  			// so update its before value with the earlier before value
   196  			if existing, ok := combinedChanges.values[key]; ok {
   197  				existing.after = valueChange.after
   198  				if existing.before.HasValue() == existing.after.HasValue() &&
   199  					bytes.Equal(existing.before.Value(), existing.after.Value()) {
   200  					// The change to this key is a no-op, so remove it from [combinedChanges].
   201  					delete(combinedChanges.values, key)
   202  					changedKeys.Remove(key)
   203  				}
   204  			} else {
   205  				combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
   206  					before: valueChange.before,
   207  					after:  valueChange.after,
   208  				}
   209  				changedKeys.Add(key)
   210  			}
   211  		}
   212  	}
   213  
   214  	// If we have <= [maxLength] elements, we're done.
   215  	if changedKeys.Len() <= maxLength {
   216  		return combinedChanges, nil
   217  	}
   218  
   219  	// Keep only the smallest [maxLength] items in [combinedChanges.values].
   220  	sortedChangedKeys := changedKeys.List()
   221  	utils.Sort(sortedChangedKeys)
   222  	for len(sortedChangedKeys) > maxLength {
   223  		greatestKey := sortedChangedKeys[len(sortedChangedKeys)-1]
   224  		sortedChangedKeys = sortedChangedKeys[:len(sortedChangedKeys)-1]
   225  		delete(combinedChanges.values, greatestKey)
   226  	}
   227  
   228  	return combinedChanges, nil
   229  }
   230  
   231  // Returns the changes to go from the current trie state back to the requested [rootID]
   232  // for the keys in [start, end].
   233  // If [start] is Nothing, all keys are considered > [start].
   234  // If [end] is Nothing, all keys are considered < [end].
   235  func (th *trieHistory) getChangesToGetToRoot(rootID ids.ID, start maybe.Maybe[[]byte], end maybe.Maybe[[]byte]) (*changeSummary, error) {
   236  	// [lastRootChange] is the last change in the history resulting in [rootID].
   237  	lastRootChange, ok := th.lastChanges[rootID]
   238  	if !ok {
   239  		return nil, ErrInsufficientHistory
   240  	}
   241  
   242  	var (
   243  		startKey                     = maybe.Bind(start, ToKey)
   244  		endKey                       = maybe.Bind(end, ToKey)
   245  		combinedChanges              = newChangeSummary(defaultPreallocationSize)
   246  		mostRecentChangeInsertNumber = th.nextInsertNumber - 1
   247  		mostRecentChangeIndex        = th.history.Len() - 1
   248  		offset                       = int(mostRecentChangeInsertNumber - lastRootChange.insertNumber)
   249  		lastRootChangeIndex          = mostRecentChangeIndex - offset
   250  	)
   251  
   252  	// Go backward from the most recent change in the history up to but
   253  	// not including the last change resulting in [rootID].
   254  	// Record each change in [combinedChanges].
   255  	for i := mostRecentChangeIndex; i > lastRootChangeIndex; i-- {
   256  		changes, _ := th.history.Index(i)
   257  
   258  		if i == mostRecentChangeIndex {
   259  			combinedChanges.rootChange.before = changes.rootChange.after
   260  		}
   261  		if i == lastRootChangeIndex+1 {
   262  			combinedChanges.rootChange.after = changes.rootChange.before
   263  		}
   264  
   265  		for key, changedNode := range changes.nodes {
   266  			combinedChanges.nodes[key] = &change[*node]{
   267  				after: changedNode.before,
   268  			}
   269  		}
   270  
   271  		for key, valueChange := range changes.values {
   272  			if (startKey.IsNothing() || !key.Less(startKey.Value())) &&
   273  				(endKey.IsNothing() || !key.Greater(endKey.Value())) {
   274  				if existing, ok := combinedChanges.values[key]; ok {
   275  					existing.after = valueChange.before
   276  				} else {
   277  					combinedChanges.values[key] = &change[maybe.Maybe[[]byte]]{
   278  						before: valueChange.after,
   279  						after:  valueChange.before,
   280  					}
   281  				}
   282  			}
   283  		}
   284  	}
   285  
   286  	return combinedChanges, nil
   287  }
   288  
   289  // record the provided set of changes in the history
   290  func (th *trieHistory) record(changes *changeSummary) {
   291  	// we aren't recording history so noop
   292  	if th.maxHistoryLen == 0 {
   293  		return
   294  	}
   295  
   296  	if th.history.Len() == th.maxHistoryLen {
   297  		// This change causes us to go over our lookback limit.
   298  		// Remove the oldest set of changes.
   299  		oldestEntry, _ := th.history.PopLeft()
   300  
   301  		latestChange := th.lastChanges[oldestEntry.rootID]
   302  		if latestChange == oldestEntry {
   303  			// The removed change was the most recent resulting in this root ID.
   304  			delete(th.lastChanges, oldestEntry.rootID)
   305  		}
   306  	}
   307  
   308  	changesAndIndex := &changeSummaryAndInsertNumber{
   309  		changeSummary: changes,
   310  		insertNumber:  th.nextInsertNumber,
   311  	}
   312  	th.nextInsertNumber++
   313  
   314  	// Add [changes] to the sorted change list.
   315  	_ = th.history.PushRight(changesAndIndex)
   316  
   317  	// Mark that this is the most recent change resulting in [changes.rootID].
   318  	th.lastChanges[changes.rootID] = changesAndIndex
   319  }