github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/storage/derived/table.go (about)

     1  package derived
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/hashicorp/go-multierror"
     8  
     9  	"github.com/onflow/flow-go/fvm/storage/errors"
    10  	"github.com/onflow/flow-go/fvm/storage/logical"
    11  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    12  	"github.com/onflow/flow-go/fvm/storage/state"
    13  )
    14  
    15  // ValueComputer is used by DerivedDataTable's GetOrCompute to compute the
    16  // derived value when the value is not in DerivedDataTable (i.e., "cache miss").
    17  type ValueComputer[TKey any, TVal any] interface {
    18  	Compute(txnState state.NestedTransactionPreparer, key TKey) (TVal, error)
    19  }
    20  
    21  type invalidatableEntry[TVal any] struct {
    22  	Value             TVal                        // immutable after initialization.
    23  	ExecutionSnapshot *snapshot.ExecutionSnapshot // immutable after initialization.
    24  
    25  	isInvalid bool // Guarded by DerivedDataTable' lock.
    26  }
    27  
    28  // DerivedDataTable is a rudimentary fork-aware OCC database table for
    29  // "caching" homogeneous (TKey, TVal) pairs for a particular block.
    30  //
    31  // The database table enforces atomicity and isolation, but not consistency and
    32  // durability.  Consistency depends on the user correctly implementing the
    33  // table's invalidator.  Durability is not needed since the values are derived
    34  // from ledger and can be computed on the fly (This assumes that recomputation
    35  // is idempotent).
    36  //
    37  // Furthermore, because data are derived, transaction validation looks
    38  // a bit unusual when compared with a textbook OCC implementation.  In
    39  // particular, the transaction's invalidator represents "real" writes to the
    40  // canonical source, whereas the transaction's readSet/writeSet entries
    41  // represent "real" reads from the canonical source.
    42  //
    43  // Multiple tables are grouped together via Validate/Commit 2 phase commit to
    44  // form the complete derived data database.
    45  type DerivedDataTable[TKey comparable, TVal any] struct {
    46  	lock  sync.RWMutex
    47  	items map[TKey]*invalidatableEntry[TVal]
    48  
    49  	latestCommitExecutionTime logical.Time
    50  
    51  	invalidators chainedTableInvalidators[TKey, TVal] // Guarded by lock.
    52  }
    53  
    54  type TableTransaction[TKey comparable, TVal any] struct {
    55  	table *DerivedDataTable[TKey, TVal]
    56  
    57  	// The start time when the snapshot first becomes readable (i.e., the
    58  	// "snapshotTime - 1"'s transaction committed the snapshot view).
    59  	snapshotTime logical.Time
    60  
    61  	// The transaction (or script)'s execution start time (aka TxIndex).
    62  	executionTime logical.Time
    63  
    64  	// toValidateTime is used to amortize cost of repeated Validate calls.
    65  	// Each Validate call will only validate the time range
    66  	// [toValidateTime, executionTime), and will advance toValidateTime to
    67  	// latestCommitExecutionTime + 1 if Validate succeeded.
    68  	//
    69  	// Note that since newly derived values are computed based on snapshotTime's
    70  	// view, each time a newly derived value is added to the transaction,
    71  	// toValidateTime is reset back to snapshotTime.
    72  	toValidateTime logical.Time
    73  
    74  	readSet  map[TKey]*invalidatableEntry[TVal]
    75  	writeSet map[TKey]*invalidatableEntry[TVal]
    76  
    77  	// When isSnapshotReadTransaction is true, invalidators must be empty.
    78  	isSnapshotReadTransaction bool
    79  	invalidators              chainedTableInvalidators[TKey, TVal]
    80  
    81  	// ignoreLatestCommitExecutionTime is used to bypass latestCommitExecutionTime checks during
    82  	// commit. This is used when operating in caching mode with scripts since "commits" are all done
    83  	// at the end of the block and are not expected to progress the execution time.
    84  	ignoreLatestCommitExecutionTime bool
    85  }
    86  
    87  func NewEmptyTable[
    88  	TKey comparable,
    89  	TVal any,
    90  ](
    91  	initialSnapshotTime logical.Time,
    92  ) *DerivedDataTable[TKey, TVal] {
    93  	return &DerivedDataTable[TKey, TVal]{
    94  		items:                     map[TKey]*invalidatableEntry[TVal]{},
    95  		latestCommitExecutionTime: initialSnapshotTime - 1,
    96  		invalidators:              nil,
    97  	}
    98  }
    99  
   100  func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] {
   101  	table.lock.RLock()
   102  	defer table.lock.RUnlock()
   103  
   104  	items := make(
   105  		map[TKey]*invalidatableEntry[TVal],
   106  		len(table.items))
   107  
   108  	for key, entry := range table.items {
   109  		// Note: We need to deep copy the invalidatableEntry here since the
   110  		// entry may be valid in the parent table, but invalid in the child
   111  		// table.
   112  		items[key] = &invalidatableEntry[TVal]{
   113  			Value:             entry.Value,
   114  			ExecutionSnapshot: entry.ExecutionSnapshot,
   115  			isInvalid:         false,
   116  		}
   117  	}
   118  
   119  	return &DerivedDataTable[TKey, TVal]{
   120  		items:                     items,
   121  		latestCommitExecutionTime: logical.ParentBlockTime,
   122  		invalidators:              nil,
   123  	}
   124  }
   125  
   126  func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 {
   127  	return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1
   128  }
   129  
   130  func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() logical.Time {
   131  	table.lock.RLock()
   132  	defer table.lock.RUnlock()
   133  
   134  	return table.latestCommitExecutionTime
   135  }
   136  
   137  func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] {
   138  	table.lock.RLock()
   139  	defer table.lock.RUnlock()
   140  
   141  	entries := make(
   142  		map[TKey]*invalidatableEntry[TVal],
   143  		len(table.items))
   144  	for key, entry := range table.items {
   145  		entries[key] = entry
   146  	}
   147  
   148  	return entries
   149  }
   150  
   151  func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] {
   152  	table.lock.RLock()
   153  	defer table.lock.RUnlock()
   154  
   155  	return table.invalidators
   156  }
   157  
   158  func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly(
   159  	key TKey,
   160  ) *invalidatableEntry[TVal] {
   161  	return table.get(key)
   162  }
   163  
   164  func (table *DerivedDataTable[TKey, TVal]) get(
   165  	key TKey,
   166  ) *invalidatableEntry[TVal] {
   167  	table.lock.RLock()
   168  	defer table.lock.RUnlock()
   169  
   170  	return table.items[key]
   171  }
   172  
   173  func (table *DerivedDataTable[TKey, TVal]) unsafeValidate(
   174  	txn *TableTransaction[TKey, TVal],
   175  ) error {
   176  	if txn.isSnapshotReadTransaction &&
   177  		txn.invalidators.ShouldInvalidateEntries() {
   178  
   179  		return fmt.Errorf(
   180  			"invalid TableTransaction: snapshot read can't invalidate")
   181  	}
   182  
   183  	if table.latestCommitExecutionTime >= txn.executionTime {
   184  		return fmt.Errorf(
   185  			"invalid TableTransaction: non-increasing time (%v >= %v)",
   186  			table.latestCommitExecutionTime,
   187  			txn.executionTime)
   188  	}
   189  
   190  	for _, entry := range txn.readSet {
   191  		if entry.isInvalid {
   192  			if txn.snapshotTime == txn.executionTime {
   193  				// This should never happen since the transaction is
   194  				// sequentially executed.
   195  				return fmt.Errorf(
   196  					"invalid TableTransaction: unrecoverable outdated read set")
   197  			}
   198  
   199  			return errors.NewRetryableConflictError(
   200  				"invalid TableTransaction: outdated read set")
   201  		}
   202  	}
   203  
   204  	applicable := table.invalidators.ApplicableInvalidators(
   205  		txn.toValidateTime)
   206  	shouldInvalidateEntries := applicable.ShouldInvalidateEntries()
   207  
   208  	for key, entry := range txn.writeSet {
   209  		current, ok := table.items[key]
   210  		if ok && current != entry {
   211  			// The derived data table must always return the same item for a given key,
   212  			// otherwise the cadence runtime will have issues comparing resolved cadence types.
   213  			//
   214  			// for example:
   215  			// two transactions are run concurrently, first loads (cadence contracts)
   216  			// A and B where B depends on A. The second transaction also loads A and C,
   217  			// where C depends on A. The first transaction commits first.
   218  			// The A from the second transaction is equivalent to the A from
   219  			// the first transaction but it is not the same object.
   220  			//
   221  			// Overwriting A with the A from the second transaction will cause program B
   222  			// to break because it will not know the types from A returned from
   223  			// the cache in the future.
   224  			// Not overwriting A will cause program C to break because it will not know
   225  			// the types from A returned from the cache in the future.
   226  			//
   227  			// The solution is to treat this as a conflict and retry the transaction.
   228  			// When the transaction is retried, the A from the first transaction will
   229  			// be used to load C in the second transaction.
   230  
   231  			return errors.NewRetryableConflictError(
   232  				"invalid TableTransaction: write conflict")
   233  		}
   234  
   235  		if !shouldInvalidateEntries ||
   236  			!applicable.ShouldInvalidateEntry(
   237  				key,
   238  				entry.Value,
   239  				entry.ExecutionSnapshot,
   240  			) {
   241  			continue
   242  		}
   243  
   244  		if txn.snapshotTime == txn.executionTime {
   245  			// This should never happen since the transaction is
   246  			// sequentially executed.
   247  			return fmt.Errorf(
   248  				"invalid TableTransaction: unrecoverable outdated " +
   249  					"write set")
   250  		}
   251  
   252  		return errors.NewRetryableConflictError(
   253  			"invalid TableTransaction: outdated write set")
   254  
   255  	}
   256  
   257  	txn.toValidateTime = table.latestCommitExecutionTime + 1
   258  
   259  	return nil
   260  }
   261  
   262  func (table *DerivedDataTable[TKey, TVal]) validate(
   263  	txn *TableTransaction[TKey, TVal],
   264  ) error {
   265  	table.lock.RLock()
   266  	defer table.lock.RUnlock()
   267  
   268  	return table.unsafeValidate(txn)
   269  }
   270  
   271  func (table *DerivedDataTable[TKey, TVal]) commit(
   272  	txn *TableTransaction[TKey, TVal],
   273  ) error {
   274  	table.lock.Lock()
   275  	defer table.lock.Unlock()
   276  
   277  	if !txn.isSnapshotReadTransaction &&
   278  		!txn.ignoreLatestCommitExecutionTime &&
   279  		table.latestCommitExecutionTime+1 < txn.snapshotTime {
   280  
   281  		return fmt.Errorf(
   282  			"invalid TableTransaction: missing commit range [%v, %v)",
   283  			table.latestCommitExecutionTime+1,
   284  			txn.snapshotTime)
   285  	}
   286  
   287  	// NOTE: Instead of throwing out all the write entries, we can commit
   288  	// the valid write entries then return error.
   289  	err := table.unsafeValidate(txn)
   290  	if err != nil {
   291  		return err
   292  	}
   293  
   294  	// Don't perform actual commit for snapshot read transaction.  This is
   295  	// safe since all values are derived from the primary source.
   296  	if txn.isSnapshotReadTransaction {
   297  		return nil
   298  	}
   299  
   300  	for key, entry := range txn.writeSet {
   301  		_, ok := table.items[key]
   302  		if ok {
   303  			// A previous transaction already committed an equivalent
   304  			// TableTransaction entry.  Since both TableTransaction entry are
   305  			// valid, just reuse the existing one for future transactions.
   306  			continue
   307  		}
   308  
   309  		table.items[key] = entry
   310  	}
   311  
   312  	if txn.invalidators.ShouldInvalidateEntries() {
   313  		for key, entry := range table.items {
   314  			if txn.invalidators.ShouldInvalidateEntry(
   315  				key,
   316  				entry.Value,
   317  				entry.ExecutionSnapshot) {
   318  
   319  				entry.isInvalid = true
   320  				delete(table.items, key)
   321  			}
   322  		}
   323  
   324  		table.invalidators = append(
   325  			table.invalidators,
   326  			txn.invalidators...)
   327  	}
   328  
   329  	table.latestCommitExecutionTime = txn.executionTime
   330  	return nil
   331  }
   332  
   333  func (table *DerivedDataTable[TKey, TVal]) newTableTransaction(
   334  	snapshotTime logical.Time,
   335  	executionTime logical.Time,
   336  	isSnapshotReadTransaction bool,
   337  	ignoreLatestCommitExecutionTime bool,
   338  ) *TableTransaction[TKey, TVal] {
   339  	return &TableTransaction[TKey, TVal]{
   340  		table:                           table,
   341  		snapshotTime:                    snapshotTime,
   342  		executionTime:                   executionTime,
   343  		toValidateTime:                  snapshotTime,
   344  		readSet:                         map[TKey]*invalidatableEntry[TVal]{},
   345  		writeSet:                        map[TKey]*invalidatableEntry[TVal]{},
   346  		isSnapshotReadTransaction:       isSnapshotReadTransaction,
   347  		ignoreLatestCommitExecutionTime: ignoreLatestCommitExecutionTime,
   348  	}
   349  }
   350  
   351  func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] {
   352  	return table.newTableTransaction(
   353  		logical.EndOfBlockExecutionTime,
   354  		logical.EndOfBlockExecutionTime,
   355  		true,
   356  		false)
   357  }
   358  
   359  func (table *DerivedDataTable[TKey, TVal]) NewCachingSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] {
   360  	return table.newTableTransaction(
   361  		logical.EndOfBlockExecutionTime,
   362  		logical.EndOfBlockExecutionTime,
   363  		false,
   364  		true)
   365  }
   366  
   367  func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction(
   368  	snapshotTime logical.Time,
   369  	executionTime logical.Time,
   370  ) (
   371  	*TableTransaction[TKey, TVal],
   372  	error,
   373  ) {
   374  	if executionTime < 0 ||
   375  		executionTime > logical.LargestNormalTransactionExecutionTime {
   376  
   377  		return nil, fmt.Errorf(
   378  			"invalid TableTransactions: execution time out of bound: %v",
   379  			executionTime)
   380  	}
   381  
   382  	if snapshotTime > executionTime {
   383  		return nil, fmt.Errorf(
   384  			"invalid TableTransactions: snapshot > execution: %v > %v",
   385  			snapshotTime,
   386  			executionTime)
   387  	}
   388  
   389  	return table.newTableTransaction(
   390  		snapshotTime,
   391  		executionTime,
   392  		false,
   393  		false), nil
   394  }
   395  
   396  // Note: use GetOrCompute instead of Get/Set whenever possible.
   397  func (txn *TableTransaction[TKey, TVal]) get(key TKey) (
   398  	TVal,
   399  	*snapshot.ExecutionSnapshot,
   400  	bool,
   401  ) {
   402  
   403  	writeEntry, ok := txn.writeSet[key]
   404  	if ok {
   405  		return writeEntry.Value, writeEntry.ExecutionSnapshot, true
   406  	}
   407  
   408  	readEntry := txn.readSet[key]
   409  	if readEntry != nil {
   410  		return readEntry.Value, readEntry.ExecutionSnapshot, true
   411  	}
   412  
   413  	readEntry = txn.table.get(key)
   414  	if readEntry != nil {
   415  		txn.readSet[key] = readEntry
   416  		return readEntry.Value, readEntry.ExecutionSnapshot, true
   417  	}
   418  
   419  	var defaultValue TVal
   420  	return defaultValue, nil, false
   421  }
   422  
   423  func (txn *TableTransaction[TKey, TVal]) GetForTestingOnly(key TKey) (
   424  	TVal,
   425  	*snapshot.ExecutionSnapshot,
   426  	bool,
   427  ) {
   428  	return txn.get(key)
   429  }
   430  
   431  func (txn *TableTransaction[TKey, TVal]) set(
   432  	key TKey,
   433  	value TVal,
   434  	snapshot *snapshot.ExecutionSnapshot,
   435  ) {
   436  	txn.writeSet[key] = &invalidatableEntry[TVal]{
   437  		Value:             value,
   438  		ExecutionSnapshot: snapshot,
   439  		isInvalid:         false,
   440  	}
   441  
   442  	// Since value is derived from snapshot's view.  We need to reset the
   443  	// toValidateTime back to snapshot time to re-validate the entry.
   444  	txn.toValidateTime = txn.snapshotTime
   445  }
   446  
   447  func (txn *TableTransaction[TKey, TVal]) SetForTestingOnly(
   448  	key TKey,
   449  	value TVal,
   450  	snapshot *snapshot.ExecutionSnapshot,
   451  ) {
   452  	txn.set(key, value, snapshot)
   453  }
   454  
   455  // GetOrCompute returns the key's value.  If a pre-computed value is available,
   456  // then the pre-computed value is returned and the cached state is replayed on
   457  // txnState.  Otherwise, the value is computed using valFunc; both the value
   458  // and the states used to compute the value are captured.
   459  //
   460  // Note: valFunc must be an idempotent function and it must not modify
   461  // txnState's values.
   462  func (txn *TableTransaction[TKey, TVal]) GetOrCompute(
   463  	txnState state.NestedTransactionPreparer,
   464  	key TKey,
   465  	computer ValueComputer[TKey, TVal],
   466  ) (
   467  	TVal,
   468  	error,
   469  ) {
   470  	var defaultVal TVal
   471  
   472  	val, state, ok := txn.get(key)
   473  	if ok {
   474  		err := txnState.AttachAndCommitNestedTransaction(state)
   475  		if err != nil {
   476  			return defaultVal, fmt.Errorf(
   477  				"failed to replay cached state: %w",
   478  				err)
   479  		}
   480  
   481  		return val, nil
   482  	}
   483  
   484  	nestedTxId, err := txnState.BeginNestedTransaction()
   485  	if err != nil {
   486  		return defaultVal, fmt.Errorf("failed to start nested txn: %w", err)
   487  	}
   488  
   489  	val, err = computer.Compute(txnState, key)
   490  
   491  	// Commit the nested transaction, even if the computation fails.
   492  	committedState, commitErr := txnState.CommitNestedTransaction(nestedTxId)
   493  	if commitErr != nil {
   494  		err = multierror.Append(err,
   495  			fmt.Errorf("failed to commit nested txn: %w", commitErr),
   496  		).ErrorOrNil()
   497  	}
   498  
   499  	if err != nil {
   500  		return defaultVal, fmt.Errorf("failed to derive value: %w", err)
   501  	}
   502  
   503  	txn.set(key, val, committedState)
   504  
   505  	return val, nil
   506  }
   507  
   508  func (txn *TableTransaction[TKey, TVal]) AddInvalidator(
   509  	invalidator TableInvalidator[TKey, TVal],
   510  ) {
   511  	if invalidator == nil || !invalidator.ShouldInvalidateEntries() {
   512  		return
   513  	}
   514  
   515  	txn.invalidators = append(
   516  		txn.invalidators,
   517  		tableInvalidatorAtTime[TKey, TVal]{
   518  			TableInvalidator: invalidator,
   519  			executionTime:    txn.executionTime,
   520  		})
   521  }
   522  
   523  func (txn *TableTransaction[TKey, TVal]) Validate() error {
   524  	return txn.table.validate(txn)
   525  }
   526  
   527  func (txn *TableTransaction[TKey, TVal]) Commit() error {
   528  	return txn.table.commit(txn)
   529  }
   530  
   531  func (txn *TableTransaction[TKey, TVal]) ToValidateTimeForTestingOnly() logical.Time {
   532  	return txn.toValidateTime
   533  }