github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/execution/computation/computer/transaction_coordinator_test.go (about)

     1  package computer
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/onflow/flow-go/fvm"
    12  	"github.com/onflow/flow-go/fvm/storage"
    13  	"github.com/onflow/flow-go/fvm/storage/logical"
    14  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    15  	"github.com/onflow/flow-go/model/flow"
    16  )
    17  
    18  type testCoordinatorVM struct{}
    19  
    20  func (testCoordinatorVM) NewExecutor(
    21  	ctx fvm.Context,
    22  	proc fvm.Procedure,
    23  	txnState storage.TransactionPreparer,
    24  ) fvm.ProcedureExecutor {
    25  	return testCoordinatorExecutor{
    26  		executionTime: proc.ExecutionTime(),
    27  	}
    28  }
    29  
    30  func (testCoordinatorVM) Run(
    31  	ctx fvm.Context,
    32  	proc fvm.Procedure,
    33  	storageSnapshot snapshot.StorageSnapshot,
    34  ) (
    35  	*snapshot.ExecutionSnapshot,
    36  	fvm.ProcedureOutput,
    37  	error,
    38  ) {
    39  	panic("not implemented")
    40  }
    41  
    42  func (testCoordinatorVM) GetAccount(
    43  	_ fvm.Context,
    44  	_ flow.Address,
    45  	_ snapshot.StorageSnapshot,
    46  ) (
    47  	*flow.Account,
    48  	error,
    49  ) {
    50  	panic("not implemented")
    51  }
    52  
    53  type testCoordinatorExecutor struct {
    54  	executionTime logical.Time
    55  }
    56  
    57  func (testCoordinatorExecutor) Cleanup() {}
    58  
    59  func (testCoordinatorExecutor) Preprocess() error {
    60  	return nil
    61  }
    62  
    63  func (testCoordinatorExecutor) Execute() error {
    64  	return nil
    65  }
    66  
    67  func (executor testCoordinatorExecutor) Output() fvm.ProcedureOutput {
    68  	return fvm.ProcedureOutput{
    69  		ComputationUsed: uint64(executor.executionTime),
    70  	}
    71  }
    72  
    73  type testCoordinator struct {
    74  	*transactionCoordinator
    75  	committed []uint64
    76  }
    77  
    78  func newTestCoordinator(t *testing.T) *testCoordinator {
    79  	db := &testCoordinator{}
    80  	db.transactionCoordinator = newTransactionCoordinator(
    81  		testCoordinatorVM{},
    82  		nil,
    83  		nil,
    84  		db)
    85  
    86  	require.Equal(t, db.SnapshotTime(), logical.Time(0))
    87  
    88  	// commit a transaction to increment the snapshot time
    89  	setupTxn, err := db.newTransaction(0)
    90  	require.NoError(t, err)
    91  
    92  	err = setupTxn.Finalize()
    93  	require.NoError(t, err)
    94  
    95  	err = setupTxn.Commit()
    96  	require.NoError(t, err)
    97  
    98  	require.Equal(t, db.SnapshotTime(), logical.Time(1))
    99  
   100  	return db
   101  
   102  }
   103  
   104  func (db *testCoordinator) AddTransactionResult(
   105  	txn TransactionRequest,
   106  	snapshot *snapshot.ExecutionSnapshot,
   107  	output fvm.ProcedureOutput,
   108  	timeSpent time.Duration,
   109  	numConflictRetries int,
   110  ) {
   111  	db.committed = append(db.committed, output.ComputationUsed)
   112  }
   113  
   114  func (db *testCoordinator) newTransaction(txnIndex uint32) (
   115  	*transaction,
   116  	error,
   117  ) {
   118  	return db.NewTransaction(
   119  		newTransactionRequest(
   120  			collectionInfo{},
   121  			fvm.NewContext(),
   122  			zerolog.Nop(),
   123  			txnIndex,
   124  			&flow.TransactionBody{},
   125  			false),
   126  		0)
   127  }
   128  
   129  type testWaitValues struct {
   130  	startTime    logical.Time
   131  	startErr     error
   132  	snapshotTime logical.Time
   133  	abortErr     error
   134  }
   135  
   136  func (db *testCoordinator) setupWait(txn *transaction) chan testWaitValues {
   137  	ret := make(chan testWaitValues, 1)
   138  	go func() {
   139  		startTime, startErr, snapshotTime, abortErr := db.waitForUpdatesNewerThan(
   140  			txn.SnapshotTime())
   141  		ret <- testWaitValues{
   142  			startTime:    startTime,
   143  			startErr:     startErr,
   144  			snapshotTime: snapshotTime,
   145  			abortErr:     abortErr,
   146  		}
   147  	}()
   148  
   149  	// Sleep a bit to ensure goroutine is running before returning the channel.
   150  	time.Sleep(10 * time.Millisecond)
   151  	return ret
   152  }
   153  
   154  func TestTransactionCoordinatorBasicCommit(t *testing.T) {
   155  	db := newTestCoordinator(t)
   156  
   157  	txns := []*transaction{}
   158  	for i := uint32(1); i < 6; i++ {
   159  		txn, err := db.newTransaction(i)
   160  		require.NoError(t, err)
   161  
   162  		txns = append(txns, txn)
   163  	}
   164  
   165  	for i, txn := range txns {
   166  		executionTime := logical.Time(1 + i)
   167  
   168  		require.Equal(t, txn.SnapshotTime(), logical.Time(1))
   169  
   170  		err := txn.Finalize()
   171  		require.NoError(t, err)
   172  
   173  		err = txn.Validate()
   174  		require.NoError(t, err)
   175  
   176  		require.Equal(t, txn.SnapshotTime(), executionTime)
   177  
   178  		err = txn.Commit()
   179  		require.NoError(t, err)
   180  
   181  		require.Equal(t, db.SnapshotTime(), executionTime+1)
   182  	}
   183  
   184  	require.Equal(t, db.committed, []uint64{0, 1, 2, 3, 4, 5})
   185  }
   186  
   187  func TestTransactionCoordinatorBlockingWaitForCommit(t *testing.T) {
   188  	db := newTestCoordinator(t)
   189  
   190  	testTxn, err := db.newTransaction(6)
   191  	require.NoError(t, err)
   192  
   193  	require.Equal(t, db.SnapshotTime(), logical.Time(1))
   194  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(1))
   195  
   196  	ret := db.setupWait(testTxn)
   197  
   198  	setupTxn, err := db.newTransaction(1)
   199  	require.NoError(t, err)
   200  
   201  	err = setupTxn.Finalize()
   202  	require.NoError(t, err)
   203  
   204  	err = setupTxn.Commit()
   205  	require.NoError(t, err)
   206  
   207  	require.Equal(t, db.SnapshotTime(), logical.Time(2))
   208  
   209  	select {
   210  	case val := <-ret:
   211  		require.Equal(
   212  			t,
   213  			val,
   214  			testWaitValues{
   215  				startTime:    1,
   216  				startErr:     nil,
   217  				snapshotTime: 2,
   218  				abortErr:     nil,
   219  			})
   220  	case <-time.After(time.Second):
   221  		require.Fail(t, "Failed to return result")
   222  	}
   223  
   224  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(1))
   225  
   226  	err = testTxn.Validate()
   227  	require.NoError(t, err)
   228  
   229  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(2))
   230  
   231  }
   232  
   233  func TestTransactionCoordinatorNonblockingWaitForCommit(t *testing.T) {
   234  	db := newTestCoordinator(t)
   235  
   236  	testTxn, err := db.newTransaction(6)
   237  	require.NoError(t, err)
   238  
   239  	setupTxn, err := db.newTransaction(1)
   240  	require.NoError(t, err)
   241  
   242  	err = setupTxn.Finalize()
   243  	require.NoError(t, err)
   244  
   245  	err = setupTxn.Commit()
   246  	require.NoError(t, err)
   247  
   248  	require.Equal(t, db.SnapshotTime(), logical.Time(2))
   249  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(1))
   250  
   251  	ret := db.setupWait(testTxn)
   252  
   253  	select {
   254  	case val := <-ret:
   255  		require.Equal(
   256  			t,
   257  			val,
   258  			testWaitValues{
   259  				startTime:    2,
   260  				startErr:     nil,
   261  				snapshotTime: 2,
   262  				abortErr:     nil,
   263  			})
   264  	case <-time.After(time.Second):
   265  		require.Fail(t, "Failed to return result")
   266  	}
   267  }
   268  
   269  func TestTransactionCoordinatorBasicAbort(t *testing.T) {
   270  	db := newTestCoordinator(t)
   271  
   272  	txn, err := db.newTransaction(1)
   273  	require.NoError(t, err)
   274  
   275  	abortErr := fmt.Errorf("abort")
   276  	db.AbortAllOutstandingTransactions(abortErr)
   277  
   278  	err = txn.Finalize()
   279  	require.NoError(t, err)
   280  
   281  	err = txn.Commit()
   282  	require.Equal(t, err, abortErr)
   283  
   284  	txn, err = db.newTransaction(2)
   285  	require.Equal(t, err, abortErr)
   286  	require.Nil(t, txn)
   287  }
   288  
   289  func TestTransactionCoordinatorBlockingWaitForAbort(t *testing.T) {
   290  	db := newTestCoordinator(t)
   291  
   292  	testTxn, err := db.newTransaction(6)
   293  	require.NoError(t, err)
   294  
   295  	// start waiting before aborting.
   296  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(1))
   297  	ret := db.setupWait(testTxn)
   298  
   299  	abortErr := fmt.Errorf("abort")
   300  	db.AbortAllOutstandingTransactions(abortErr)
   301  
   302  	select {
   303  	case val := <-ret:
   304  		require.Equal(
   305  			t,
   306  			val,
   307  			testWaitValues{
   308  				startTime:    1,
   309  				startErr:     nil,
   310  				snapshotTime: 1,
   311  				abortErr:     abortErr,
   312  			})
   313  	case <-time.After(time.Second):
   314  		require.Fail(t, "Failed to return result")
   315  	}
   316  
   317  	err = testTxn.Finalize()
   318  	require.NoError(t, err)
   319  
   320  	err = testTxn.Commit()
   321  	require.Equal(t, err, abortErr)
   322  }
   323  
   324  func TestTransactionCoordinatorNonblockingWaitForAbort(t *testing.T) {
   325  	db := newTestCoordinator(t)
   326  
   327  	testTxn, err := db.newTransaction(6)
   328  	require.NoError(t, err)
   329  
   330  	// start aborting before waiting.
   331  	abortErr := fmt.Errorf("abort")
   332  	db.AbortAllOutstandingTransactions(abortErr)
   333  
   334  	require.Equal(t, testTxn.SnapshotTime(), logical.Time(1))
   335  	ret := db.setupWait(testTxn)
   336  
   337  	select {
   338  	case val := <-ret:
   339  		require.Equal(
   340  			t,
   341  			val,
   342  			testWaitValues{
   343  				startTime:    1,
   344  				startErr:     abortErr,
   345  				snapshotTime: 1,
   346  				abortErr:     abortErr,
   347  			})
   348  	case <-time.After(time.Second):
   349  		require.Fail(t, "Failed to return result")
   350  	}
   351  
   352  	err = testTxn.Finalize()
   353  	require.NoError(t, err)
   354  
   355  	err = testTxn.Commit()
   356  	require.Equal(t, err, abortErr)
   357  }