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

     1  package primary
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/onflow/flow-go/fvm/storage/errors"
     8  	"github.com/onflow/flow-go/fvm/storage/logical"
     9  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    10  	"github.com/onflow/flow-go/fvm/storage/state"
    11  	"github.com/onflow/flow-go/model/flow"
    12  )
    13  
    14  const (
    15  	conflictErrorTemplate = "invalid transaction: committed txn %d conflicts " +
    16  		"with executing txn %d with snapshot at %d (Conflicting register: %v)"
    17  )
    18  
    19  // BlockData is a rudimentary in-memory MVCC database for storing (RegisterID,
    20  // RegisterValue) pairs for a particular block.  The database enforces
    21  // atomicity, consistency, and isolation, but not durability (The transactions
    22  // are made durable by the block computer using aggregated execution snapshots).
    23  type BlockData struct {
    24  	mutex sync.RWMutex
    25  
    26  	latestSnapshot timestampedSnapshotTree // Guarded by mutex
    27  }
    28  
    29  type TransactionData struct {
    30  	block *BlockData
    31  
    32  	executionTime             logical.Time
    33  	isSnapshotReadTransaction bool
    34  
    35  	snapshot *rebaseableTimestampedSnapshotTree
    36  
    37  	state.NestedTransactionPreparer
    38  
    39  	finalizedExecutionSnapshot *snapshot.ExecutionSnapshot
    40  }
    41  
    42  // Note: storageSnapshot must be thread safe.
    43  func NewBlockData(
    44  	storageSnapshot snapshot.StorageSnapshot,
    45  	snapshotTime logical.Time,
    46  ) *BlockData {
    47  	return &BlockData{
    48  		latestSnapshot: newTimestampedSnapshotTree(
    49  			storageSnapshot,
    50  			logical.Time(snapshotTime)),
    51  	}
    52  }
    53  
    54  func (block *BlockData) LatestSnapshot() timestampedSnapshotTree {
    55  	block.mutex.RLock()
    56  	defer block.mutex.RUnlock()
    57  
    58  	return block.latestSnapshot
    59  }
    60  
    61  func (block *BlockData) newTransactionData(
    62  	isSnapshotReadTransaction bool,
    63  	executionTime logical.Time,
    64  	parameters state.StateParameters,
    65  ) *TransactionData {
    66  	snapshot := newRebaseableTimestampedSnapshotTree(block.LatestSnapshot())
    67  	return &TransactionData{
    68  		block:                     block,
    69  		executionTime:             executionTime,
    70  		snapshot:                  snapshot,
    71  		isSnapshotReadTransaction: isSnapshotReadTransaction,
    72  		NestedTransactionPreparer: state.NewTransactionState(
    73  			snapshot,
    74  			parameters),
    75  	}
    76  }
    77  
    78  func (block *BlockData) NewTransactionData(
    79  	executionTime logical.Time,
    80  	parameters state.StateParameters,
    81  ) (
    82  	*TransactionData,
    83  	error,
    84  ) {
    85  	if executionTime < 0 ||
    86  		executionTime > logical.LargestNormalTransactionExecutionTime {
    87  
    88  		return nil, fmt.Errorf(
    89  			"invalid transaction: execution time out of bound")
    90  	}
    91  
    92  	txn := block.newTransactionData(
    93  		false,
    94  		executionTime,
    95  		parameters)
    96  
    97  	if txn.SnapshotTime() > executionTime {
    98  		return nil, fmt.Errorf(
    99  			"invalid transaction: snapshot > execution: %v > %v",
   100  			txn.SnapshotTime(),
   101  			executionTime)
   102  	}
   103  
   104  	return txn, nil
   105  }
   106  
   107  func (block *BlockData) NewCachingSnapshotReadTransactionData(
   108  	parameters state.StateParameters,
   109  ) *TransactionData {
   110  	return block.newTransactionData(
   111  		false,
   112  		logical.EndOfBlockExecutionTime,
   113  		parameters)
   114  }
   115  
   116  func (block *BlockData) NewSnapshotReadTransactionData(
   117  	parameters state.StateParameters,
   118  ) *TransactionData {
   119  	return block.newTransactionData(
   120  		true,
   121  		logical.EndOfBlockExecutionTime,
   122  		parameters)
   123  }
   124  
   125  func (txn *TransactionData) SnapshotTime() logical.Time {
   126  	return txn.snapshot.SnapshotTime()
   127  }
   128  
   129  func (txn *TransactionData) validate(
   130  	latestSnapshot timestampedSnapshotTree,
   131  ) error {
   132  	validatedSnapshotTime := txn.SnapshotTime()
   133  
   134  	if latestSnapshot.SnapshotTime() <= validatedSnapshotTime {
   135  		// transaction's snapshot is up-to-date.
   136  		return nil
   137  	}
   138  
   139  	var readSet map[flow.RegisterID]struct{}
   140  	if txn.finalizedExecutionSnapshot != nil {
   141  		readSet = txn.finalizedExecutionSnapshot.ReadSet
   142  	} else {
   143  		readSet = txn.InterimReadSet()
   144  	}
   145  
   146  	updates, err := latestSnapshot.UpdatesSince(validatedSnapshotTime)
   147  	if err != nil {
   148  		return fmt.Errorf("invalid transaction: %w", err)
   149  	}
   150  
   151  	for i, writeSet := range updates {
   152  		hasConflict, registerId := intersect(writeSet, readSet)
   153  		if hasConflict {
   154  			return errors.NewRetryableConflictError(
   155  				conflictErrorTemplate,
   156  				validatedSnapshotTime+logical.Time(i),
   157  				txn.executionTime,
   158  				validatedSnapshotTime,
   159  				registerId)
   160  		}
   161  	}
   162  
   163  	txn.snapshot.Rebase(latestSnapshot)
   164  	return nil
   165  }
   166  
   167  func (txn *TransactionData) Validate() error {
   168  	return txn.validate(txn.block.LatestSnapshot())
   169  }
   170  
   171  func (txn *TransactionData) Finalize() error {
   172  	executionSnapshot, err := txn.FinalizeMainTransaction()
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	// NOTE: Since cadence does not support the notion of read only execution,
   178  	// snapshot read transaction execution can inadvertently produce a non-empty
   179  	// write set.  We'll just drop these updates.
   180  	if txn.isSnapshotReadTransaction {
   181  		executionSnapshot.WriteSet = nil
   182  	}
   183  
   184  	txn.finalizedExecutionSnapshot = executionSnapshot
   185  	return nil
   186  }
   187  
   188  func (block *BlockData) commit(txn *TransactionData) error {
   189  	if txn.finalizedExecutionSnapshot == nil {
   190  		return fmt.Errorf("invalid transaction: transaction not finalized.")
   191  	}
   192  
   193  	block.mutex.Lock()
   194  	defer block.mutex.Unlock()
   195  
   196  	err := txn.validate(block.latestSnapshot)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	// Don't perform actual commit for snapshot read transaction since they
   202  	// do not advance logical time.
   203  	if txn.isSnapshotReadTransaction {
   204  		return nil
   205  	}
   206  
   207  	latestSnapshotTime := block.latestSnapshot.SnapshotTime()
   208  
   209  	if latestSnapshotTime < txn.executionTime {
   210  		// i.e., transactions are committed out-of-order.
   211  		return fmt.Errorf(
   212  			"invalid transaction: missing commit range [%v, %v)",
   213  			latestSnapshotTime,
   214  			txn.executionTime)
   215  	}
   216  
   217  	if block.latestSnapshot.SnapshotTime() > txn.executionTime {
   218  		// i.e., re-commiting an already committed transaction.
   219  		return fmt.Errorf(
   220  			"invalid transaction: non-increasing time (%v >= %v)",
   221  			latestSnapshotTime-1,
   222  			txn.executionTime)
   223  	}
   224  
   225  	block.latestSnapshot = block.latestSnapshot.Append(
   226  		txn.finalizedExecutionSnapshot)
   227  
   228  	return nil
   229  }
   230  
   231  func (txn *TransactionData) Commit() (
   232  	*snapshot.ExecutionSnapshot,
   233  	error,
   234  ) {
   235  	err := txn.block.commit(txn)
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	return txn.finalizedExecutionSnapshot, nil
   241  }