github.com/koko1123/flow-go-1@v0.29.6/fvm/derived/table.go (about)

     1  package derived
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/koko1123/flow-go-1/fvm/state"
     8  )
     9  
    10  // ValueComputer is used by DerivedDataTable's GetOrCompute to compute the
    11  // derived value when the value is not in DerivedDataTable (i.e., "cache miss").
    12  type ValueComputer[TKey any, TVal any] interface {
    13  	Compute(txnState *state.TransactionState, key TKey) (TVal, error)
    14  }
    15  
    16  type invalidatableEntry[TVal any] struct {
    17  	Value TVal         // immutable after initialization.
    18  	State *state.State // immutable after initialization.
    19  
    20  	isInvalid bool // Guarded by DerivedDataTable' lock.
    21  }
    22  
    23  // DerivedDataTable is a rudimentary fork-aware OCC database table for
    24  // "caching" homogeneous (TKey, TVal) pairs for a particular block.
    25  //
    26  // The database table enforces atomicity and isolation, but not consistency and
    27  // durability.  Consistency depends on the user correctly implementing the
    28  // table's invalidator.  Durability is not needed since the values are derived
    29  // from ledger and can be computed on the fly (This assumes that recomputation
    30  // is idempotent).
    31  //
    32  // Furthermore, because data are derived, transaction validation looks
    33  // a bit unusual when compared with a textbook OCC implementation.  In
    34  // particular, the transaction's invalidator represents "real" writes to the
    35  // canonical source, whereas the transaction's readSet/writeSet entries
    36  // represent "real" reads from the canonical source.
    37  //
    38  // Multiple tables are grouped together via Validate/Commit 2 phase commit to
    39  // form the complete derived data database.
    40  type DerivedDataTable[TKey comparable, TVal any] struct {
    41  	lock  sync.RWMutex
    42  	items map[TKey]*invalidatableEntry[TVal]
    43  
    44  	latestCommitExecutionTime LogicalTime
    45  
    46  	invalidators chainedTableInvalidators[TKey, TVal] // Guarded by lock.
    47  }
    48  
    49  type TableTransaction[TKey comparable, TVal any] struct {
    50  	table *DerivedDataTable[TKey, TVal]
    51  
    52  	// The start time when the snapshot first becomes readable (i.e., the
    53  	// "snapshotTime - 1"'s transaction committed the snapshot view)
    54  	snapshotTime LogicalTime
    55  
    56  	// The transaction (or script)'s execution start time (aka TxIndex).
    57  	executionTime LogicalTime
    58  
    59  	readSet  map[TKey]*invalidatableEntry[TVal]
    60  	writeSet map[TKey]*invalidatableEntry[TVal]
    61  
    62  	// When isSnapshotReadTransaction is true, invalidators must be empty.
    63  	isSnapshotReadTransaction bool
    64  	invalidators              chainedTableInvalidators[TKey, TVal]
    65  }
    66  
    67  func newEmptyTable[TKey comparable, TVal any](
    68  	latestCommit LogicalTime,
    69  ) *DerivedDataTable[TKey, TVal] {
    70  	return &DerivedDataTable[TKey, TVal]{
    71  		items:                     map[TKey]*invalidatableEntry[TVal]{},
    72  		latestCommitExecutionTime: latestCommit,
    73  		invalidators:              nil,
    74  	}
    75  }
    76  
    77  func NewEmptyTable[TKey comparable, TVal any]() *DerivedDataTable[TKey, TVal] {
    78  	return newEmptyTable[TKey, TVal](ParentBlockTime)
    79  }
    80  
    81  // This variant is needed by the chunk verifier, which does not start at the
    82  // beginning of the block.
    83  func NewEmptyTableWithOffset[TKey comparable, TVal any](offset uint32) *DerivedDataTable[TKey, TVal] {
    84  	return newEmptyTable[TKey, TVal](LogicalTime(offset) - 1)
    85  }
    86  
    87  func (table *DerivedDataTable[TKey, TVal]) NewChildTable() *DerivedDataTable[TKey, TVal] {
    88  	table.lock.RLock()
    89  	defer table.lock.RUnlock()
    90  
    91  	items := make(
    92  		map[TKey]*invalidatableEntry[TVal],
    93  		len(table.items))
    94  
    95  	for key, entry := range table.items {
    96  		// Note: We need to deep copy the invalidatableEntry here since the
    97  		// entry may be valid in the parent table, but invalid in the child
    98  		// table.
    99  		items[key] = &invalidatableEntry[TVal]{
   100  			Value:     entry.Value,
   101  			State:     entry.State,
   102  			isInvalid: false,
   103  		}
   104  	}
   105  
   106  	return &DerivedDataTable[TKey, TVal]{
   107  		items:                     items,
   108  		latestCommitExecutionTime: ParentBlockTime,
   109  		invalidators:              nil,
   110  	}
   111  }
   112  
   113  func (table *DerivedDataTable[TKey, TVal]) NextTxIndexForTestingOnly() uint32 {
   114  	return uint32(table.LatestCommitExecutionTimeForTestingOnly()) + 1
   115  }
   116  
   117  func (table *DerivedDataTable[TKey, TVal]) LatestCommitExecutionTimeForTestingOnly() LogicalTime {
   118  	table.lock.RLock()
   119  	defer table.lock.RUnlock()
   120  
   121  	return table.latestCommitExecutionTime
   122  }
   123  
   124  func (table *DerivedDataTable[TKey, TVal]) EntriesForTestingOnly() map[TKey]*invalidatableEntry[TVal] {
   125  	table.lock.RLock()
   126  	defer table.lock.RUnlock()
   127  
   128  	entries := make(
   129  		map[TKey]*invalidatableEntry[TVal],
   130  		len(table.items))
   131  	for key, entry := range table.items {
   132  		entries[key] = entry
   133  	}
   134  
   135  	return entries
   136  }
   137  
   138  func (table *DerivedDataTable[TKey, TVal]) InvalidatorsForTestingOnly() chainedTableInvalidators[TKey, TVal] {
   139  	table.lock.RLock()
   140  	defer table.lock.RUnlock()
   141  
   142  	return table.invalidators
   143  }
   144  
   145  func (table *DerivedDataTable[TKey, TVal]) GetForTestingOnly(
   146  	key TKey,
   147  ) *invalidatableEntry[TVal] {
   148  	return table.get(key)
   149  }
   150  
   151  func (table *DerivedDataTable[TKey, TVal]) get(
   152  	key TKey,
   153  ) *invalidatableEntry[TVal] {
   154  	table.lock.RLock()
   155  	defer table.lock.RUnlock()
   156  
   157  	return table.items[key]
   158  }
   159  
   160  func (table *DerivedDataTable[TKey, TVal]) unsafeValidate(
   161  	item *TableTransaction[TKey, TVal],
   162  ) RetryableError {
   163  	if item.isSnapshotReadTransaction &&
   164  		item.invalidators.ShouldInvalidateEntries() {
   165  
   166  		return newNotRetryableError(
   167  			"invalid TableTransaction: snapshot read can't invalidate")
   168  	}
   169  
   170  	if table.latestCommitExecutionTime >= item.executionTime {
   171  		return newNotRetryableError(
   172  			"invalid TableTransaction: non-increasing time (%v >= %v)",
   173  			table.latestCommitExecutionTime,
   174  			item.executionTime)
   175  	}
   176  
   177  	if table.latestCommitExecutionTime+1 < item.snapshotTime &&
   178  		(!item.isSnapshotReadTransaction ||
   179  			item.snapshotTime != EndOfBlockExecutionTime) {
   180  
   181  		return newNotRetryableError(
   182  			"invalid TableTransaction: missing commit range [%v, %v)",
   183  			table.latestCommitExecutionTime+1,
   184  			item.snapshotTime)
   185  	}
   186  
   187  	for _, entry := range item.readSet {
   188  		if entry.isInvalid {
   189  			return newRetryableError(
   190  				"invalid TableTransactions. outdated read set")
   191  		}
   192  	}
   193  
   194  	applicable := table.invalidators.ApplicableInvalidators(
   195  		item.snapshotTime)
   196  	if applicable.ShouldInvalidateEntries() {
   197  		for key, entry := range item.writeSet {
   198  			if applicable.ShouldInvalidateEntry(key, entry.Value, entry.State) {
   199  				return newRetryableError(
   200  					"invalid TableTransactions. outdated write set")
   201  			}
   202  		}
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  func (table *DerivedDataTable[TKey, TVal]) validate(
   209  	item *TableTransaction[TKey, TVal],
   210  ) RetryableError {
   211  	table.lock.RLock()
   212  	defer table.lock.RUnlock()
   213  
   214  	return table.unsafeValidate(item)
   215  }
   216  
   217  func (table *DerivedDataTable[TKey, TVal]) commit(
   218  	txn *TableTransaction[TKey, TVal],
   219  ) RetryableError {
   220  	table.lock.Lock()
   221  	defer table.lock.Unlock()
   222  
   223  	// NOTE: Instead of throwing out all the write entries, we can commit
   224  	// the valid write entries then return error.
   225  	err := table.unsafeValidate(txn)
   226  	if err != nil {
   227  		return err
   228  	}
   229  
   230  	for key, entry := range txn.writeSet {
   231  		_, ok := table.items[key]
   232  		if ok {
   233  			// A previous transaction already committed an equivalent TableTransaction
   234  			// entry.  Since both TableTransaction entry are valid, just reuse the
   235  			// existing one for future transactions.
   236  			continue
   237  		}
   238  
   239  		table.items[key] = entry
   240  	}
   241  
   242  	if txn.invalidators.ShouldInvalidateEntries() {
   243  		for key, entry := range table.items {
   244  			if txn.invalidators.ShouldInvalidateEntry(
   245  				key,
   246  				entry.Value,
   247  				entry.State) {
   248  
   249  				entry.isInvalid = true
   250  				delete(table.items, key)
   251  			}
   252  		}
   253  
   254  		table.invalidators = append(
   255  			table.invalidators,
   256  			txn.invalidators...)
   257  	}
   258  
   259  	// NOTE: We cannot advance commit time when we encounter a snapshot read
   260  	// (aka script) transaction since these transactions don't generate new
   261  	// snapshots.  It is safe to commit the entries since snapshot read
   262  	// transactions never invalidate entries.
   263  	if !txn.isSnapshotReadTransaction {
   264  		table.latestCommitExecutionTime = txn.executionTime
   265  	}
   266  	return nil
   267  }
   268  
   269  func (table *DerivedDataTable[TKey, TVal]) newTableTransaction(
   270  	upperBoundExecutionTime LogicalTime,
   271  	snapshotTime LogicalTime,
   272  	executionTime LogicalTime,
   273  	isSnapshotReadTransaction bool,
   274  ) (
   275  	*TableTransaction[TKey, TVal],
   276  	error,
   277  ) {
   278  	if executionTime < 0 || executionTime > upperBoundExecutionTime {
   279  		return nil, fmt.Errorf(
   280  			"invalid TableTransactions: execution time out of bound: %v",
   281  			executionTime)
   282  	}
   283  
   284  	if snapshotTime > executionTime {
   285  		return nil, fmt.Errorf(
   286  			"invalid TableTransactions: snapshot > execution: %v > %v",
   287  			snapshotTime,
   288  			executionTime)
   289  	}
   290  
   291  	return &TableTransaction[TKey, TVal]{
   292  		table:                     table,
   293  		snapshotTime:              snapshotTime,
   294  		executionTime:             executionTime,
   295  		readSet:                   map[TKey]*invalidatableEntry[TVal]{},
   296  		writeSet:                  map[TKey]*invalidatableEntry[TVal]{},
   297  		isSnapshotReadTransaction: isSnapshotReadTransaction,
   298  	}, nil
   299  }
   300  
   301  func (table *DerivedDataTable[TKey, TVal]) NewSnapshotReadTableTransaction(
   302  	snapshotTime LogicalTime,
   303  	executionTime LogicalTime,
   304  ) (
   305  	*TableTransaction[TKey, TVal],
   306  	error,
   307  ) {
   308  	return table.newTableTransaction(
   309  		LargestSnapshotReadTransactionExecutionTime,
   310  		snapshotTime,
   311  		executionTime,
   312  		true)
   313  }
   314  
   315  func (table *DerivedDataTable[TKey, TVal]) NewTableTransaction(
   316  	snapshotTime LogicalTime,
   317  	executionTime LogicalTime,
   318  ) (
   319  	*TableTransaction[TKey, TVal],
   320  	error,
   321  ) {
   322  	return table.newTableTransaction(
   323  		LargestNormalTransactionExecutionTime,
   324  		snapshotTime,
   325  		executionTime,
   326  		false)
   327  }
   328  
   329  // Note: use GetOrCompute instead of Get/Set whenever possible.
   330  func (txn *TableTransaction[TKey, TVal]) Get(key TKey) (
   331  	TVal,
   332  	*state.State,
   333  	bool,
   334  ) {
   335  
   336  	writeEntry, ok := txn.writeSet[key]
   337  	if ok {
   338  		return writeEntry.Value, writeEntry.State, true
   339  	}
   340  
   341  	readEntry := txn.readSet[key]
   342  	if readEntry != nil {
   343  		return readEntry.Value, readEntry.State, true
   344  	}
   345  
   346  	readEntry = txn.table.get(key)
   347  	if readEntry != nil {
   348  		txn.readSet[key] = readEntry
   349  		return readEntry.Value, readEntry.State, true
   350  	}
   351  
   352  	var defaultValue TVal
   353  	return defaultValue, nil, false
   354  }
   355  
   356  // Note: use GetOrCompute instead of Get/Set whenever possible.
   357  func (txn *TableTransaction[TKey, TVal]) Set(
   358  	key TKey,
   359  	value TVal,
   360  	state *state.State,
   361  ) {
   362  	txn.writeSet[key] = &invalidatableEntry[TVal]{
   363  		Value:     value,
   364  		State:     state,
   365  		isInvalid: false,
   366  	}
   367  }
   368  
   369  // GetOrCompute returns the key's value.  If a pre-computed value is available,
   370  // then the pre-computed value is returned and the cached state is replayed on
   371  // txnState.  Otherwise, the value is computed using valFunc; both the value
   372  // and the states used to compute the value are captured.
   373  //
   374  // Note: valFunc must be an idempotent function and it must not modify
   375  // txnState's values.
   376  func (txn *TableTransaction[TKey, TVal]) GetOrCompute(
   377  	txnState *state.TransactionState,
   378  	key TKey,
   379  	computer ValueComputer[TKey, TVal],
   380  ) (
   381  	TVal,
   382  	error,
   383  ) {
   384  	var defaultVal TVal
   385  
   386  	val, state, ok := txn.Get(key)
   387  	if ok {
   388  		err := txnState.AttachAndCommit(state)
   389  		if err != nil {
   390  			return defaultVal, fmt.Errorf(
   391  				"failed to replay cached state: %w",
   392  				err)
   393  		}
   394  
   395  		return val, nil
   396  	}
   397  
   398  	nestedTxId, err := txnState.BeginNestedTransaction()
   399  	if err != nil {
   400  		return defaultVal, fmt.Errorf("failed to start nested txn: %w", err)
   401  	}
   402  
   403  	val, err = computer.Compute(txnState, key)
   404  	if err != nil {
   405  		return defaultVal, fmt.Errorf("failed to derive value: %w", err)
   406  	}
   407  
   408  	committedState, err := txnState.Commit(nestedTxId)
   409  	if err != nil {
   410  		return defaultVal, fmt.Errorf("failed to commit nested txn: %w", err)
   411  	}
   412  
   413  	txn.Set(key, val, committedState)
   414  
   415  	return val, nil
   416  }
   417  
   418  func (txn *TableTransaction[TKey, TVal]) AddInvalidator(
   419  	invalidator TableInvalidator[TKey, TVal],
   420  ) {
   421  	if invalidator == nil || !invalidator.ShouldInvalidateEntries() {
   422  		return
   423  	}
   424  
   425  	txn.invalidators = append(
   426  		txn.invalidators,
   427  		tableInvalidatorAtTime[TKey, TVal]{
   428  			TableInvalidator: invalidator,
   429  			executionTime:    txn.executionTime,
   430  		})
   431  }
   432  
   433  func (txn *TableTransaction[TKey, TVal]) Validate() RetryableError {
   434  	return txn.table.validate(txn)
   435  }
   436  
   437  func (txn *TableTransaction[TKey, TVal]) Commit() RetryableError {
   438  	return txn.table.commit(txn)
   439  }