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 }