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  }