github.com/onflow/flow-go@v0.33.17/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 migrations []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, and %d migrations have been planed", 343 state.String(), 344 len(migrations), 345 ) 346 347 // get trie 348 t, err := l.forest.GetTrie(ledger.RootHash(state)) 349 if err != nil { 350 rh, _ := l.forest.MostRecentTouchedRootHash() 351 l.logger.Info(). 352 Str("hash", rh.String()). 353 Msgf("Most recently touched root hash.") 354 return nil, 355 fmt.Errorf("cannot get trie at the given state commitment: %w", err) 356 } 357 358 // clean up tries to release memory 359 err = l.keepOnlyOneTrie(state) 360 if err != nil { 361 return nil, 362 fmt.Errorf("failed to clean up tries to reduce memory usage: %w", err) 363 } 364 365 var payloads []*ledger.Payload 366 var newTrie *trie.MTrie 367 368 noMigration := len(migrations) == 0 369 370 if noMigration { 371 // when there is no migration, reuse the trie without rebuilding it 372 newTrie = t 373 } else { 374 // get all payloads 375 payloads = t.AllPayloads() 376 payloadSize := len(payloads) 377 378 // migrate payloads 379 for i, migrate := range migrations { 380 l.logger.Info().Msgf("migration %d/%d is underway", i, len(migrations)) 381 382 start := time.Now() 383 payloads, err = migrate(payloads) 384 elapsed := time.Since(start) 385 386 if err != nil { 387 return nil, fmt.Errorf("error applying migration (%d): %w", i, err) 388 } 389 390 newPayloadSize := len(payloads) 391 392 if payloadSize != newPayloadSize { 393 l.logger.Warn(). 394 Int("migration_step", i). 395 Int("expected_size", payloadSize). 396 Int("outcome_size", newPayloadSize). 397 Msg("payload counts has changed during migration, make sure this is expected.") 398 } 399 l.logger.Info().Str("timeTaken", elapsed.String()).Msgf("migration %d is done", i) 400 401 payloadSize = newPayloadSize 402 } 403 404 l.logger.Info().Msgf("creating paths for %v payloads", len(payloads)) 405 406 // get paths 407 paths, err := pathfinder.PathsFromPayloads(payloads, targetPathFinderVersion) 408 if err != nil { 409 return nil, fmt.Errorf("cannot export checkpoint, can't construct paths: %w", err) 410 } 411 412 l.logger.Info().Msgf("constructing a new trie with migrated payloads (count: %d)...", len(payloads)) 413 414 emptyTrie := trie.NewEmptyMTrie() 415 416 derefPayloads := make([]ledger.Payload, len(payloads)) 417 for i, p := range payloads { 418 derefPayloads[i] = *p 419 } 420 421 // no need to prune the data since it has already been prunned through migrations 422 applyPruning := false 423 newTrie, _, err = trie.NewTrieWithUpdatedRegisters(emptyTrie, paths, derefPayloads, applyPruning) 424 if err != nil { 425 return nil, fmt.Errorf("constructing updated trie failed: %w", err) 426 } 427 } 428 429 statecommitment := ledger.State(newTrie.RootHash()) 430 431 l.logger.Info().Msgf("successfully built new trie. NEW ROOT STATECOMMIEMENT: %v", statecommitment.String()) 432 433 return newTrie, nil 434 } 435 436 // MostRecentTouchedState returns a state which is most recently touched. 437 func (l *Ledger) MostRecentTouchedState() (ledger.State, error) { 438 root, err := l.forest.MostRecentTouchedRootHash() 439 return ledger.State(root), err 440 } 441 442 // HasState returns true if the given state exists inside the ledger 443 func (l *Ledger) HasState(state ledger.State) bool { 444 return l.forest.HasTrie(ledger.RootHash(state)) 445 } 446 447 // DumpTrieAsJSON export trie at specific state as JSONL (each line is JSON encoding of a payload) 448 func (l *Ledger) DumpTrieAsJSON(state ledger.State, writer io.Writer) error { 449 fmt.Println(ledger.RootHash(state)) 450 trie, err := l.forest.GetTrie(ledger.RootHash(state)) 451 if err != nil { 452 return fmt.Errorf("cannot find the target trie: %w", err) 453 } 454 return trie.DumpAsJSON(writer) 455 } 456 457 // this operation should only be used for exporting 458 func (l *Ledger) keepOnlyOneTrie(state ledger.State) error { 459 // don't write things to WALs 460 l.wal.PauseRecord() 461 defer l.wal.UnpauseRecord() 462 return l.forest.PurgeCacheExcept(ledger.RootHash(state)) 463 } 464 465 // FindTrieByStateCommit iterates over the ledger tries and compares the root hash to the state commitment 466 // if a match is found it is returned, otherwise a nil value is returned indicating no match was found 467 func (l *Ledger) FindTrieByStateCommit(commitment flow.StateCommitment) (*trie.MTrie, error) { 468 tries, err := l.Tries() 469 if err != nil { 470 return nil, err 471 } 472 473 for _, t := range tries { 474 if t.RootHash().Equals(ledger.RootHash(commitment)) { 475 return t, nil 476 } 477 } 478 479 return nil, nil 480 }