github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/ledger/complete/ledger.go (about) 1 package complete 2 3 import ( 4 "fmt" 5 "io" 6 "time" 7 8 "github.com/rs/zerolog" 9 10 "github.com/onflow/flow-go/ledger" 11 "github.com/onflow/flow-go/ledger/common/hash" 12 "github.com/onflow/flow-go/ledger/common/pathfinder" 13 "github.com/onflow/flow-go/ledger/complete/mtrie" 14 "github.com/onflow/flow-go/ledger/complete/mtrie/trie" 15 realWAL "github.com/onflow/flow-go/ledger/complete/wal" 16 "github.com/onflow/flow-go/model/flow" 17 "github.com/onflow/flow-go/module" 18 ) 19 20 const ( 21 DefaultCacheSize = 1000 22 DefaultPathFinderVersion = 1 23 defaultTrieUpdateChanSize = 500 24 ) 25 26 // Ledger (complete) is a fast memory-efficient fork-aware thread-safe trie-based key/value storage. 27 // Ledger holds an array of registers (key-value pairs) and keeps tracks of changes over a limited time. 28 // Each register is referenced by an ID (key) and holds a value (byte slice). 29 // Ledger provides atomic batched updates and read (with or without proofs) operation given a list of keys. 30 // Every update to the Ledger creates a new state which captures the state of the storage. 31 // Under the hood, it uses binary Merkle tries to generate inclusion and non-inclusion proofs. 32 // Ledger is fork-aware which means any update can be applied at any previous state which forms a tree of tries (forest). 33 // The forest is in memory but all changes (e.g. register updates) are captured inside write-ahead-logs for crash recovery reasons. 34 // In order to limit the memory usage and maintain the performance storage only keeps a limited number of 35 // tries and purge the old ones (FIFO-based); in other words, Ledger is not designed to be used 36 // for archival usage but make it possible for other software components to reconstruct very old tries using write-ahead logs. 37 type Ledger struct { 38 forest *mtrie.Forest 39 wal realWAL.LedgerWAL 40 metrics module.LedgerMetrics 41 logger zerolog.Logger 42 trieUpdateCh chan *WALTrieUpdate 43 pathFinderVersion uint8 44 } 45 46 // NewLedger creates a new in-memory trie-backed ledger storage with persistence. 47 func NewLedger( 48 wal realWAL.LedgerWAL, 49 capacity int, 50 metrics module.LedgerMetrics, 51 log zerolog.Logger, 52 pathFinderVer uint8) (*Ledger, error) { 53 54 logger := log.With().Str("ledger_mod", "complete").Logger() 55 56 forest, err := mtrie.NewForest(capacity, metrics, nil) 57 if err != nil { 58 return nil, fmt.Errorf("cannot create forest: %w", err) 59 } 60 61 storage := &Ledger{ 62 forest: forest, 63 wal: wal, 64 metrics: metrics, 65 logger: logger, 66 pathFinderVersion: pathFinderVer, 67 trieUpdateCh: make(chan *WALTrieUpdate, defaultTrieUpdateChanSize), 68 } 69 70 // pause records to prevent double logging trie removals 71 wal.PauseRecord() 72 defer wal.UnpauseRecord() 73 74 err = wal.ReplayOnForest(forest) 75 if err != nil { 76 return nil, fmt.Errorf("cannot restore LedgerWAL: %w", err) 77 } 78 79 wal.UnpauseRecord() 80 81 // TODO update to proper value once https://github.com/onflow/flow-go/pull/3720 is merged 82 metrics.ForestApproxMemorySize(0) 83 84 return storage, nil 85 } 86 87 // TrieUpdateChan returns a channel which is used to receive trie updates that needs to be logged in WALs. 88 // This channel is closed when ledger component shutdowns down. 89 func (l *Ledger) TrieUpdateChan() <-chan *WALTrieUpdate { 90 return l.trieUpdateCh 91 } 92 93 // Ready implements interface module.ReadyDoneAware 94 // it starts the EventLoop's internal processing loop. 95 func (l *Ledger) Ready() <-chan struct{} { 96 ready := make(chan struct{}) 97 go func() { 98 defer close(ready) 99 // Start WAL component. 100 <-l.wal.Ready() 101 }() 102 return ready 103 } 104 105 // Done implements interface module.ReadyDoneAware 106 func (l *Ledger) Done() <-chan struct{} { 107 done := make(chan struct{}) 108 go func() { 109 defer close(done) 110 111 // Ledger is responsible for closing trieUpdateCh channel, 112 // so Compactor can drain and process remaining updates. 113 close(l.trieUpdateCh) 114 }() 115 return done 116 } 117 118 // InitialState returns the state of an empty ledger 119 func (l *Ledger) InitialState() ledger.State { 120 return ledger.State(l.forest.GetEmptyRootHash()) 121 } 122 123 // ValueSizes read the values of the given keys at the given state. 124 // It returns value sizes in the same order as given registerIDs and errors (if any) 125 func (l *Ledger) ValueSizes(query *ledger.Query) (valueSizes []int, err error) { 126 start := time.Now() 127 paths, err := pathfinder.KeysToPaths(query.Keys(), l.pathFinderVersion) 128 if err != nil { 129 return nil, err 130 } 131 trieRead := &ledger.TrieRead{RootHash: ledger.RootHash(query.State()), Paths: paths} 132 valueSizes, err = l.forest.ValueSizes(trieRead) 133 if err != nil { 134 return nil, err 135 } 136 137 l.metrics.ReadValuesNumber(uint64(len(paths))) 138 readDuration := time.Since(start) 139 l.metrics.ReadDuration(readDuration) 140 141 if len(paths) > 0 { 142 durationPerValue := time.Duration(readDuration.Nanoseconds()/int64(len(paths))) * time.Nanosecond 143 l.metrics.ReadDurationPerItem(durationPerValue) 144 } 145 146 return valueSizes, err 147 } 148 149 // GetSingleValue reads value of a single given key at the given state. 150 func (l *Ledger) GetSingleValue(query *ledger.QuerySingleValue) (value ledger.Value, err error) { 151 start := time.Now() 152 path, err := pathfinder.KeyToPath(query.Key(), l.pathFinderVersion) 153 if err != nil { 154 return nil, err 155 } 156 trieRead := &ledger.TrieReadSingleValue{RootHash: ledger.RootHash(query.State()), Path: path} 157 value, err = l.forest.ReadSingleValue(trieRead) 158 if err != nil { 159 return nil, err 160 } 161 162 l.metrics.ReadValuesNumber(1) 163 readDuration := time.Since(start) 164 l.metrics.ReadDuration(readDuration) 165 166 durationPerValue := time.Duration(readDuration.Nanoseconds()) * time.Nanosecond 167 l.metrics.ReadDurationPerItem(durationPerValue) 168 169 return value, nil 170 } 171 172 // Get read the values of the given keys at the given state 173 // it returns the values in the same order as given registerIDs and errors (if any) 174 func (l *Ledger) Get(query *ledger.Query) (values []ledger.Value, err error) { 175 start := time.Now() 176 paths, err := pathfinder.KeysToPaths(query.Keys(), l.pathFinderVersion) 177 if err != nil { 178 return nil, err 179 } 180 trieRead := &ledger.TrieRead{RootHash: ledger.RootHash(query.State()), Paths: paths} 181 values, err = l.forest.Read(trieRead) 182 if err != nil { 183 return nil, err 184 } 185 186 l.metrics.ReadValuesNumber(uint64(len(paths))) 187 readDuration := time.Since(start) 188 l.metrics.ReadDuration(readDuration) 189 190 if len(paths) > 0 { 191 durationPerValue := time.Duration(readDuration.Nanoseconds()/int64(len(paths))) * time.Nanosecond 192 l.metrics.ReadDurationPerItem(durationPerValue) 193 } 194 195 return values, err 196 } 197 198 // Set updates the ledger given an update. 199 // It returns the state after update and errors (if any) 200 func (l *Ledger) Set(update *ledger.Update) (newState ledger.State, trieUpdate *ledger.TrieUpdate, err error) { 201 if update.Size() == 0 { 202 return update.State(), 203 &ledger.TrieUpdate{ 204 RootHash: ledger.RootHash(update.State()), 205 Paths: []ledger.Path{}, 206 Payloads: []*ledger.Payload{}, 207 }, 208 nil 209 } 210 211 start := time.Now() 212 213 trieUpdate, err = pathfinder.UpdateToTrieUpdate(update, l.pathFinderVersion) 214 if err != nil { 215 return ledger.State(hash.DummyHash), nil, err 216 } 217 218 l.metrics.UpdateCount() 219 220 newState, err = l.set(trieUpdate) 221 if err != nil { 222 return ledger.State(hash.DummyHash), nil, err 223 } 224 225 // TODO update to proper value once https://github.com/onflow/flow-go/pull/3720 is merged 226 l.metrics.ForestApproxMemorySize(0) 227 228 elapsed := time.Since(start) 229 l.metrics.UpdateDuration(elapsed) 230 231 if len(trieUpdate.Paths) > 0 { 232 durationPerValue := time.Duration(elapsed.Nanoseconds() / int64(len(trieUpdate.Paths))) 233 l.metrics.UpdateDurationPerItem(durationPerValue) 234 } 235 236 state := update.State() 237 l.logger.Info().Hex("from", state[:]). 238 Hex("to", newState[:]). 239 Int("update_size", update.Size()). 240 Msg("ledger updated") 241 return newState, trieUpdate, nil 242 } 243 244 func (l *Ledger) set(trieUpdate *ledger.TrieUpdate) (newState ledger.State, err error) { 245 246 // resultCh is a buffered channel to receive WAL update result. 247 resultCh := make(chan error, 1) 248 249 // trieCh is a buffered channel to send updated trie. 250 // trieCh can be closed without sending updated trie to indicate failure to update trie. 251 trieCh := make(chan *trie.MTrie, 1) 252 defer close(trieCh) 253 254 // There are two goroutines: 255 // 1. writing the trie update to WAL (in Compactor goroutine) 256 // 2. creating a new trie from the trie update (in this goroutine) 257 // Since writing to WAL is running concurrently, we use resultCh 258 // to receive WAL update result from Compactor. 259 // Compactor also needs new trie created here because Compactor 260 // caches new trie to minimize memory foot-print while checkpointing. 261 // `trieCh` is used to send created trie to Compactor. 262 l.trieUpdateCh <- &WALTrieUpdate{Update: trieUpdate, ResultCh: resultCh, TrieCh: trieCh} 263 264 newTrie, err := l.forest.NewTrie(trieUpdate) 265 walError := <-resultCh 266 267 if err != nil { 268 return ledger.State(hash.DummyHash), fmt.Errorf("cannot update state: %w", err) 269 } 270 if walError != nil { 271 return ledger.State(hash.DummyHash), fmt.Errorf("error while writing LedgerWAL: %w", walError) 272 } 273 274 err = l.forest.AddTrie(newTrie) 275 if err != nil { 276 return ledger.State(hash.DummyHash), fmt.Errorf("failed to add new trie to forest: %w", err) 277 } 278 279 trieCh <- newTrie 280 281 return ledger.State(newTrie.RootHash()), nil 282 } 283 284 // Prove provides proofs for a ledger query and errors (if any). 285 // 286 // Proves are generally _not_ provided in the register order of the query. 287 // In the current implementation, proofs are sorted in a deterministic order specified by the 288 // forest and mtrie implementation. 289 func (l *Ledger) Prove(query *ledger.Query) (proof ledger.Proof, err error) { 290 291 paths, err := pathfinder.KeysToPaths(query.Keys(), l.pathFinderVersion) 292 if err != nil { 293 return nil, err 294 } 295 296 trieRead := &ledger.TrieRead{RootHash: ledger.RootHash(query.State()), Paths: paths} 297 batchProof, err := l.forest.Proofs(trieRead) 298 if err != nil { 299 return nil, fmt.Errorf("could not get proofs: %w", err) 300 } 301 302 proofToGo := ledger.EncodeTrieBatchProof(batchProof) 303 304 if len(paths) > 0 { 305 l.metrics.ProofSize(uint32(len(proofToGo) / len(paths))) 306 } 307 308 return proofToGo, err 309 } 310 311 // MemSize return the amount of memory used by ledger 312 // TODO implement an approximate MemSize method 313 func (l *Ledger) MemSize() (int64, error) { 314 return 0, nil 315 } 316 317 // ForestSize returns the number of tries stored in the forest 318 func (l *Ledger) ForestSize() int { 319 return l.forest.Size() 320 } 321 322 // Tries returns the tries stored in the forest 323 func (l *Ledger) Tries() ([]*trie.MTrie, error) { 324 return l.forest.GetTries() 325 } 326 327 // Checkpointer returns a checkpointer instance 328 func (l *Ledger) Checkpointer() (*realWAL.Checkpointer, error) { 329 checkpointer, err := l.wal.NewCheckpointer() 330 if err != nil { 331 return nil, fmt.Errorf("cannot create checkpointer for compactor: %w", err) 332 } 333 return checkpointer, nil 334 } 335 336 func (l *Ledger) MigrateAt( 337 state ledger.State, 338 migration ledger.Migration, 339 targetPathFinderVersion uint8, 340 ) (*trie.MTrie, error) { 341 l.logger.Info().Msgf( 342 "Ledger is loaded, checkpoint export has started for state %s", 343 state.String(), 344 ) 345 346 // get trie 347 t, err := l.forest.GetTrie(ledger.RootHash(state)) 348 if err != nil { 349 rh, _ := l.forest.MostRecentTouchedRootHash() 350 l.logger.Info(). 351 Str("hash", rh.String()). 352 Msgf("Most recently touched root hash.") 353 return nil, 354 fmt.Errorf("cannot get trie at the given state commitment: %w", err) 355 } 356 357 // clean up tries to release memory 358 err = l.keepOnlyOneTrie(state) 359 if err != nil { 360 return nil, 361 fmt.Errorf("failed to clean up tries to reduce memory usage: %w", err) 362 } 363 364 var payloads []*ledger.Payload 365 var newTrie *trie.MTrie 366 367 if migration == nil { 368 // when there is no migration, reuse the trie without rebuilding it 369 newTrie = t 370 } else { 371 // get all payloads 372 payloads = t.AllPayloads() 373 payloads, err = migration(payloads) 374 if err != nil { 375 return nil, fmt.Errorf("error applying migration: %w", err) 376 } 377 378 l.logger.Info().Msgf("creating paths for %v payloads", len(payloads)) 379 380 // get paths 381 paths, err := pathfinder.PathsFromPayloads(payloads, targetPathFinderVersion) 382 if err != nil { 383 return nil, fmt.Errorf("cannot export checkpoint, can't construct paths: %w", err) 384 } 385 386 l.logger.Info().Msgf("constructing a new trie with migrated payloads (count: %d)...", len(payloads)) 387 388 emptyTrie := trie.NewEmptyMTrie() 389 390 derefPayloads := make([]ledger.Payload, len(payloads)) 391 for i, p := range payloads { 392 derefPayloads[i] = *p 393 } 394 395 // no need to prune the data since it has already been prunned through migrations 396 const applyPruning = false 397 newTrie, _, err = trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, derefPayloads, applyPruning) 398 if err != nil { 399 return nil, fmt.Errorf("constructing updated trie failed: %w", err) 400 } 401 } 402 403 stateCommitment := ledger.State(newTrie.RootHash()) 404 405 l.logger.Info().Msgf("successfully built new trie. NEW ROOT STATECOMMIEMENT: %v", stateCommitment.String()) 406 407 return newTrie, nil 408 } 409 410 // MostRecentTouchedState returns a state which is most recently touched. 411 func (l *Ledger) MostRecentTouchedState() (ledger.State, error) { 412 root, err := l.forest.MostRecentTouchedRootHash() 413 return ledger.State(root), err 414 } 415 416 // HasState returns true if the given state exists inside the ledger 417 func (l *Ledger) HasState(state ledger.State) bool { 418 return l.forest.HasTrie(ledger.RootHash(state)) 419 } 420 421 // DumpTrieAsJSON export trie at specific state as JSONL (each line is JSON encoding of a payload) 422 func (l *Ledger) DumpTrieAsJSON(state ledger.State, writer io.Writer) error { 423 fmt.Println(ledger.RootHash(state)) 424 trie, err := l.forest.GetTrie(ledger.RootHash(state)) 425 if err != nil { 426 return fmt.Errorf("cannot find the target trie: %w", err) 427 } 428 return trie.DumpAsJSON(writer) 429 } 430 431 // this operation should only be used for exporting 432 func (l *Ledger) keepOnlyOneTrie(state ledger.State) error { 433 // don't write things to WALs 434 l.wal.PauseRecord() 435 defer l.wal.UnpauseRecord() 436 return l.forest.PurgeCacheExcept(ledger.RootHash(state)) 437 } 438 439 // FindTrieByStateCommit iterates over the ledger tries and compares the root hash to the state commitment 440 // if a match is found it is returned, otherwise a nil value is returned indicating no match was found 441 func (l *Ledger) FindTrieByStateCommit(commitment flow.StateCommitment) (*trie.MTrie, error) { 442 tries, err := l.Tries() 443 if err != nil { 444 return nil, err 445 } 446 447 for _, t := range tries { 448 if t.RootHash().Equals(ledger.RootHash(commitment)) { 449 return t, nil 450 } 451 } 452 453 return nil, nil 454 }