github.com/onflow/flow-go@v0.33.17/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 tranaction: 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) NewSnapshotReadTransactionData(
   108  	parameters state.StateParameters,
   109  ) *TransactionData {
   110  	return block.newTransactionData(
   111  		true,
   112  		logical.EndOfBlockExecutionTime,
   113  		parameters)
   114  }
   115  
   116  func (txn *TransactionData) SnapshotTime() logical.Time {
   117  	return txn.snapshot.SnapshotTime()
   118  }
   119  
   120  func (txn *TransactionData) validate(
   121  	latestSnapshot timestampedSnapshotTree,
   122  ) error {
   123  	validatedSnapshotTime := txn.SnapshotTime()
   124  
   125  	if latestSnapshot.SnapshotTime() <= validatedSnapshotTime {
   126  		// transaction's snapshot is up-to-date.
   127  		return nil
   128  	}
   129  
   130  	var readSet map[flow.RegisterID]struct{}
   131  	if txn.finalizedExecutionSnapshot != nil {
   132  		readSet = txn.finalizedExecutionSnapshot.ReadSet
   133  	} else {
   134  		readSet = txn.InterimReadSet()
   135  	}
   136  
   137  	updates, err := latestSnapshot.UpdatesSince(validatedSnapshotTime)
   138  	if err != nil {
   139  		return fmt.Errorf("invalid transaction: %w", err)
   140  	}
   141  
   142  	for i, writeSet := range updates {
   143  		hasConflict, registerId := intersect(writeSet, readSet)
   144  		if hasConflict {
   145  			return errors.NewRetryableConflictError(
   146  				conflictErrorTemplate,
   147  				validatedSnapshotTime+logical.Time(i),
   148  				txn.executionTime,
   149  				validatedSnapshotTime,
   150  				registerId)
   151  		}
   152  	}
   153  
   154  	txn.snapshot.Rebase(latestSnapshot)
   155  	return nil
   156  }
   157  
   158  func (txn *TransactionData) Validate() error {
   159  	return txn.validate(txn.block.LatestSnapshot())
   160  }
   161  
   162  func (txn *TransactionData) Finalize() error {
   163  	executionSnapshot, err := txn.FinalizeMainTransaction()
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	// NOTE: Since cadence does not support the notion of read only execution,
   169  	// snapshot read transaction execution can inadvertently produce a non-empty
   170  	// write set.  We'll just drop these updates.
   171  	if txn.isSnapshotReadTransaction {
   172  		executionSnapshot.WriteSet = nil
   173  	}
   174  
   175  	txn.finalizedExecutionSnapshot = executionSnapshot
   176  	return nil
   177  }
   178  
   179  func (block *BlockData) commit(txn *TransactionData) error {
   180  	if txn.finalizedExecutionSnapshot == nil {
   181  		return fmt.Errorf("invalid transaction: transaction not finalized.")
   182  	}
   183  
   184  	block.mutex.Lock()
   185  	defer block.mutex.Unlock()
   186  
   187  	err := txn.validate(block.latestSnapshot)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	// Don't perform actual commit for snapshot read transaction since they
   193  	// do not advance logical time.
   194  	if txn.isSnapshotReadTransaction {
   195  		return nil
   196  	}
   197  
   198  	latestSnapshotTime := block.latestSnapshot.SnapshotTime()
   199  
   200  	if latestSnapshotTime < txn.executionTime {
   201  		// i.e., transactions are committed out-of-order.
   202  		return fmt.Errorf(
   203  			"invalid transaction: missing commit range [%v, %v)",
   204  			latestSnapshotTime,
   205  			txn.executionTime)
   206  	}
   207  
   208  	if block.latestSnapshot.SnapshotTime() > txn.executionTime {
   209  		// i.e., re-commiting an already committed transaction.
   210  		return fmt.Errorf(
   211  			"invalid transaction: non-increasing time (%v >= %v)",
   212  			latestSnapshotTime-1,
   213  			txn.executionTime)
   214  	}
   215  
   216  	block.latestSnapshot = block.latestSnapshot.Append(
   217  		txn.finalizedExecutionSnapshot)
   218  
   219  	return nil
   220  }
   221  
   222  func (txn *TransactionData) Commit() (
   223  	*snapshot.ExecutionSnapshot,
   224  	error,
   225  ) {
   226  	err := txn.block.commit(txn)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	return txn.finalizedExecutionSnapshot, nil
   232  }