github.com/onflow/flow-go@v0.33.17/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  
    82  func NewEmptyTable[
    83  	TKey comparable,
    84  	TVal any,
    85  ](
    86  	initialSnapshotTime logical.Time,
    87  ) *DerivedDataTable[TKey, TVal] {
    88  	return &DerivedDataTable[TKey, TVal]{
    89  		items:                     map[TKey]*invalidatableEntry[TVal]{},
    90  		latestCommitExecutionTime: initialSnapshotTime - 1,
    91  		invalidators:              nil,
    92  	}
    93  }
    94  
    95  func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] {
    96  	table.lock.RLock()
    97  	defer table.lock.RUnlock()
    98  
    99  	items := make(
   100  		map[TKey]*invalidatableEntry[TVal],
   101  		len(table.items))
   102  
   103  	for key, entry := range table.items {
   104  		// Note: We need to deep copy the invalidatableEntry here since the
   105  		// entry may be valid in the parent table, but invalid in the child
   106  		// table.
   107  		items[key] = &invalidatableEntry[TVal]{
   108  			Value:             entry.Value,
   109  			ExecutionSnapshot: entry.ExecutionSnapshot,
   110  			isInvalid:         false,
   111  		}
   112  	}
   113  
   114  	return &DerivedDataTable[TKey, TVal]{
   115  		items:                     items,
   116  		latestCommitExecutionTime: logical.ParentBlockTime,
   117  		invalidators:              nil,
   118  	}
   119  }
   120  
   121  func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 {
   122  	return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1
   123  }
   124  
   125  func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() logical.Time {
   126  	table.lock.RLock()
   127  	defer table.lock.RUnlock()
   128  
   129  	return table.latestCommitExecutionTime
   130  }
   131  
   132  func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] {
   133  	table.lock.RLock()
   134  	defer table.lock.RUnlock()
   135  
   136  	entries := make(
   137  		map[TKey]*invalidatableEntry[TVal],
   138  		len(table.items))
   139  	for key, entry := range table.items {
   140  		entries[key] = entry
   141  	}
   142  
   143  	return entries
   144  }
   145  
   146  func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] {
   147  	table.lock.RLock()
   148  	defer table.lock.RUnlock()
   149  
   150  	return table.invalidators
   151  }
   152  
   153  func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly(
   154  	key TKey,
   155  ) *invalidatableEntry[TVal] {
   156  	return table.get(key)
   157  }
   158  
   159  func (table *DerivedDataTable[TKey, TVal]) get(
   160  	key TKey,
   161  ) *invalidatableEntry[TVal] {
   162  	table.lock.RLock()
   163  	defer table.lock.RUnlock()
   164  
   165  	return table.items[key]
   166  }
   167  
   168  func (table *DerivedDataTable[TKey, TVal]) unsafeValidate(
   169  	txn *TableTransaction[TKey, TVal],
   170  ) error {
   171  	if txn.isSnapshotReadTransaction &&
   172  		txn.invalidators.ShouldInvalidateEntries() {
   173  
   174  		return fmt.Errorf(
   175  			"invalid TableTransaction: snapshot read can't invalidate")
   176  	}
   177  
   178  	if table.latestCommitExecutionTime >= txn.executionTime {
   179  		return fmt.Errorf(
   180  			"invalid TableTransaction: non-increasing time (%v >= %v)",
   181  			table.latestCommitExecutionTime,
   182  			txn.executionTime)
   183  	}
   184  
   185  	for _, entry := range txn.readSet {
   186  		if entry.isInvalid {
   187  			if txn.snapshotTime == txn.executionTime {
   188  				// This should never happen since the transaction is
   189  				// sequentially executed.
   190  				return fmt.Errorf(
   191  					"invalid TableTransaction: unrecoverable outdated read set")
   192  			}
   193  
   194  			return errors.NewRetryableConflictError(
   195  				"invalid TableTransaction: outdated read set")
   196  		}
   197  	}
   198  
   199  	applicable := table.invalidators.ApplicableInvalidators(
   200  		txn.toValidateTime)
   201  	shouldInvalidateEntries := applicable.ShouldInvalidateEntries()
   202  
   203  	for key, entry := range txn.writeSet {
   204  		current, ok := table.items[key]
   205  		if ok && current != entry {
   206  			// The derived data table must always return the same item for a given key,
   207  			// otherwise the cadence runtime will have issues comparing resolved cadence types.
   208  			//
   209  			// for example:
   210  			// two transactions are run concurrently, first loads (cadence contracts)
   211  			// A and B where B depends on A. The second transaction also loads A and C,
   212  			// where C depends on A. The first transaction commits first.
   213  			// The A from the second transaction is equivalent to the A from
   214  			// the first transaction but it is not the same object.
   215  			//
   216  			// Overwriting A with the A from the second transaction will cause program B
   217  			// to break because it will not know the types from A returned from
   218  			// the cache in the future.
   219  			// Not overwriting A will cause program C to break because it will not know
   220  			// the types from A returned from the cache in the future.
   221  			//
   222  			// The solution is to treat this as a conflict and retry the transaction.
   223  			// When the transaction is retried, the A from the first transaction will
   224  			// be used to load C in the second transaction.
   225  
   226  			return errors.NewRetryableConflictError(
   227  				"invalid TableTransaction: write conflict")
   228  		}
   229  
   230  		if !shouldInvalidateEntries ||
   231  			!applicable.ShouldInvalidateEntry(
   232  				key,
   233  				entry.Value,
   234  				entry.ExecutionSnapshot,
   235  			) {
   236  			continue
   237  		}
   238  
   239  		if txn.snapshotTime == txn.executionTime {
   240  			// This should never happen since the transaction is
   241  			// sequentially executed.
   242  			return fmt.Errorf(
   243  				"invalid TableTransaction: unrecoverable outdated " +
   244  					"write set")
   245  		}
   246  
   247  		return errors.NewRetryableConflictError(
   248  			"invalid TableTransaction: outdated write set")
   249  
   250  	}
   251  
   252  	txn.toValidateTime = table.latestCommitExecutionTime + 1
   253  
   254  	return nil
   255  }
   256  
   257  func (table *DerivedDataTable[TKey, TVal]) validate(
   258  	txn *TableTransaction[TKey, TVal],
   259  ) error {
   260  	table.lock.RLock()
   261  	defer table.lock.RUnlock()
   262  
   263  	return table.unsafeValidate(txn)
   264  }
   265  
   266  func (table *DerivedDataTable[TKey, TVal]) commit(
   267  	txn *TableTransaction[TKey, TVal],
   268  ) error {
   269  	table.lock.Lock()
   270  	defer table.lock.Unlock()
   271  
   272  	if !txn.isSnapshotReadTransaction &&
   273  		table.latestCommitExecutionTime+1 < txn.snapshotTime {
   274  
   275  		return fmt.Errorf(
   276  			"invalid TableTransaction: missing commit range [%v, %v)",
   277  			table.latestCommitExecutionTime+1,
   278  			txn.snapshotTime)
   279  	}
   280  
   281  	// NOTE: Instead of throwing out all the write entries, we can commit
   282  	// the valid write entries then return error.
   283  	err := table.unsafeValidate(txn)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	// Don't perform actual commit for snapshot read transaction.  This is
   289  	// safe since all values are derived from the primary source.
   290  	if txn.isSnapshotReadTransaction {
   291  		return nil
   292  	}
   293  
   294  	for key, entry := range txn.writeSet {
   295  		_, ok := table.items[key]
   296  		if ok {
   297  			// A previous transaction already committed an equivalent
   298  			// TableTransaction entry.  Since both TableTransaction entry are
   299  			// valid, just reuse the existing one for future transactions.
   300  			continue
   301  		}
   302  
   303  		table.items[key] = entry
   304  	}
   305  
   306  	if txn.invalidators.ShouldInvalidateEntries() {
   307  		for key, entry := range table.items {
   308  			if txn.invalidators.ShouldInvalidateEntry(
   309  				key,
   310  				entry.Value,
   311  				entry.ExecutionSnapshot) {
   312  
   313  				entry.isInvalid = true
   314  				delete(table.items, key)
   315  			}
   316  		}
   317  
   318  		table.invalidators = append(
   319  			table.invalidators,
   320  			txn.invalidators...)
   321  	}
   322  
   323  	table.latestCommitExecutionTime = txn.executionTime
   324  	return nil
   325  }
   326  
   327  func (table *DerivedDataTable[TKey, TVal]) newTableTransaction(
   328  	snapshotTime logical.Time,
   329  	executionTime logical.Time,
   330  	isSnapshotReadTransaction bool,
   331  ) *TableTransaction[TKey, TVal] {
   332  	return &TableTransaction[TKey, TVal]{
   333  		table:                     table,
   334  		snapshotTime:              snapshotTime,
   335  		executionTime:             executionTime,
   336  		toValidateTime:            snapshotTime,
   337  		readSet:                   map[TKey]*invalidatableEntry[TVal]{},
   338  		writeSet:                  map[TKey]*invalidatableEntry[TVal]{},
   339  		isSnapshotReadTransaction: isSnapshotReadTransaction,
   340  	}
   341  }
   342  
   343  func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction() *TableTransaction[TKey, TVal] {
   344  	return table.newTableTransaction(
   345  		logical.EndOfBlockExecutionTime,
   346  		logical.EndOfBlockExecutionTime,
   347  		true)
   348  }
   349  
   350  func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction(
   351  	snapshotTime logical.Time,
   352  	executionTime logical.Time,
   353  ) (
   354  	*TableTransaction[TKey, TVal],
   355  	error,
   356  ) {
   357  	if executionTime < 0 ||
   358  		executionTime > logical.LargestNormalTransactionExecutionTime {
   359  
   360  		return nil, fmt.Errorf(
   361  			"invalid TableTransactions: execution time out of bound: %v",
   362  			executionTime)
   363  	}
   364  
   365  	if snapshotTime > executionTime {
   366  		return nil, fmt.Errorf(
   367  			"invalid TableTransactions: snapshot > execution: %v > %v",
   368  			snapshotTime,
   369  			executionTime)
   370  	}
   371  
   372  	return table.newTableTransaction(
   373  		snapshotTime,
   374  		executionTime,
   375  		false), nil
   376  }
   377  
   378  // Note: use GetOrCompute instead of Get/Set whenever possible.
   379  func (txn *TableTransaction[TKey, TVal]) get(key TKey) (
   380  	TVal,
   381  	*snapshot.ExecutionSnapshot,
   382  	bool,
   383  ) {
   384  
   385  	writeEntry, ok := txn.writeSet[key]
   386  	if ok {
   387  		return writeEntry.Value, writeEntry.ExecutionSnapshot, true
   388  	}
   389  
   390  	readEntry := txn.readSet[key]
   391  	if readEntry != nil {
   392  		return readEntry.Value, readEntry.ExecutionSnapshot, true
   393  	}
   394  
   395  	readEntry = txn.table.get(key)
   396  	if readEntry != nil {
   397  		txn.readSet[key] = readEntry
   398  		return readEntry.Value, readEntry.ExecutionSnapshot, true
   399  	}
   400  
   401  	var defaultValue TVal
   402  	return defaultValue, nil, false
   403  }
   404  
   405  func (txn *TableTransaction[TKey, TVal]) GetForTestingOnly(key TKey) (
   406  	TVal,
   407  	*snapshot.ExecutionSnapshot,
   408  	bool,
   409  ) {
   410  	return txn.get(key)
   411  }
   412  
   413  func (txn *TableTransaction[TKey, TVal]) set(
   414  	key TKey,
   415  	value TVal,
   416  	snapshot *snapshot.ExecutionSnapshot,
   417  ) {
   418  	txn.writeSet[key] = &invalidatableEntry[TVal]{
   419  		Value:             value,
   420  		ExecutionSnapshot: snapshot,
   421  		isInvalid:         false,
   422  	}
   423  
   424  	// Since value is derived from snapshot's view.  We need to reset the
   425  	// toValidateTime back to snapshot time to re-validate the entry.
   426  	txn.toValidateTime = txn.snapshotTime
   427  }
   428  
   429  func (txn *TableTransaction[TKey, TVal]) SetForTestingOnly(
   430  	key TKey,
   431  	value TVal,
   432  	snapshot *snapshot.ExecutionSnapshot,
   433  ) {
   434  	txn.set(key, value, snapshot)
   435  }
   436  
   437  // GetOrCompute returns the key's value.  If a pre-computed value is available,
   438  // then the pre-computed value is returned and the cached state is replayed on
   439  // txnState.  Otherwise, the value is computed using valFunc; both the value
   440  // and the states used to compute the value are captured.
   441  //
   442  // Note: valFunc must be an idempotent function and it must not modify
   443  // txnState's values.
   444  func (txn *TableTransaction[TKey, TVal]) GetOrCompute(
   445  	txnState state.NestedTransactionPreparer,
   446  	key TKey,
   447  	computer ValueComputer[TKey, TVal],
   448  ) (
   449  	TVal,
   450  	error,
   451  ) {
   452  	var defaultVal TVal
   453  
   454  	val, state, ok := txn.get(key)
   455  	if ok {
   456  		err := txnState.AttachAndCommitNestedTransaction(state)
   457  		if err != nil {
   458  			return defaultVal, fmt.Errorf(
   459  				"failed to replay cached state: %w",
   460  				err)
   461  		}
   462  
   463  		return val, nil
   464  	}
   465  
   466  	nestedTxId, err := txnState.BeginNestedTransaction()
   467  	if err != nil {
   468  		return defaultVal, fmt.Errorf("failed to start nested txn: %w", err)
   469  	}
   470  
   471  	val, err = computer.Compute(txnState, key)
   472  
   473  	// Commit the nested transaction, even if the computation fails.
   474  	committedState, commitErr := txnState.CommitNestedTransaction(nestedTxId)
   475  	if commitErr != nil {
   476  		err = multierror.Append(err,
   477  			fmt.Errorf("failed to commit nested txn: %w", commitErr),
   478  		).ErrorOrNil()
   479  	}
   480  
   481  	if err != nil {
   482  		return defaultVal, fmt.Errorf("failed to derive value: %w", err)
   483  	}
   484  
   485  	txn.set(key, val, committedState)
   486  
   487  	return val, nil
   488  }
   489  
   490  func (txn *TableTransaction[TKey, TVal]) AddInvalidator(
   491  	invalidator TableInvalidator[TKey, TVal],
   492  ) {
   493  	if invalidator == nil || !invalidator.ShouldInvalidateEntries() {
   494  		return
   495  	}
   496  
   497  	txn.invalidators = append(
   498  		txn.invalidators,
   499  		tableInvalidatorAtTime[TKey, TVal]{
   500  			TableInvalidator: invalidator,
   501  			executionTime:    txn.executionTime,
   502  		})
   503  }
   504  
   505  func (txn *TableTransaction[TKey, TVal]) Validate() error {
   506  	return txn.table.validate(txn)
   507  }
   508  
   509  func (txn *TableTransaction[TKey, TVal]) Commit() error {
   510  	return txn.table.commit(txn)
   511  }
   512  
   513  func (txn *TableTransaction[TKey, TVal]) ToValidateTimeForTestingOnly() logical.Time {
   514  	return txn.toValidateTime
   515  }