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

     1  package primary
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     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  	"github.com/onflow/flow-go/model/flow"
    14  )
    15  
    16  func TestBlockDataWithTransactionOffset(t *testing.T) {
    17  	key := flow.RegisterID{
    18  		Owner: "",
    19  		Key:   "key",
    20  	}
    21  	expectedValue := flow.RegisterValue([]byte("value"))
    22  
    23  	snapshotTime := logical.Time(18)
    24  
    25  	block := NewBlockData(
    26  		snapshot.MapStorageSnapshot{
    27  			key: expectedValue,
    28  		},
    29  		snapshotTime)
    30  
    31  	snapshot := block.LatestSnapshot()
    32  	require.Equal(t, snapshotTime, snapshot.SnapshotTime())
    33  
    34  	value, err := snapshot.Get(key)
    35  	require.NoError(t, err)
    36  	require.Equal(t, expectedValue, value)
    37  }
    38  
    39  func TestBlockDataNormalTransactionInvalidExecutionTime(t *testing.T) {
    40  	snapshotTime := logical.Time(5)
    41  	block := NewBlockData(nil, snapshotTime)
    42  
    43  	txn, err := block.NewTransactionData(-1, state.DefaultParameters())
    44  	require.ErrorContains(t, err, "execution time out of bound")
    45  	require.Nil(t, txn)
    46  
    47  	txn, err = block.NewTransactionData(
    48  		logical.EndOfBlockExecutionTime,
    49  		state.DefaultParameters())
    50  	require.ErrorContains(t, err, "execution time out of bound")
    51  	require.Nil(t, txn)
    52  
    53  	txn, err = block.NewTransactionData(
    54  		snapshotTime-1,
    55  		state.DefaultParameters())
    56  	require.ErrorContains(t, err, "snapshot > execution: 5 > 4")
    57  	require.Nil(t, txn)
    58  }
    59  
    60  func testBlockDataValidate(
    61  	t *testing.T,
    62  	shouldFinalize bool,
    63  ) {
    64  	baseSnapshotTime := logical.Time(11)
    65  	block := NewBlockData(nil, baseSnapshotTime)
    66  
    67  	// Commit a key before the actual test txn (which read the same key).
    68  
    69  	testSetupTxn, err := block.NewTransactionData(
    70  		baseSnapshotTime,
    71  		state.DefaultParameters())
    72  	require.NoError(t, err)
    73  
    74  	registerId1 := flow.RegisterID{
    75  		Owner: "",
    76  		Key:   "key1",
    77  	}
    78  	expectedValue1 := flow.RegisterValue([]byte("value1"))
    79  
    80  	err = testSetupTxn.Set(registerId1, expectedValue1)
    81  	require.NoError(t, err)
    82  
    83  	err = testSetupTxn.Finalize()
    84  	require.NoError(t, err)
    85  
    86  	_, err = testSetupTxn.Commit()
    87  	require.NoError(t, err)
    88  
    89  	require.Equal(
    90  		t,
    91  		baseSnapshotTime+1,
    92  		block.LatestSnapshot().SnapshotTime())
    93  
    94  	value, err := block.LatestSnapshot().Get(registerId1)
    95  	require.NoError(t, err)
    96  	require.Equal(t, expectedValue1, value)
    97  
    98  	// Start the test transaction at an "older" snapshot to ensure valdiate
    99  	// works as expected.
   100  
   101  	testTxn, err := block.NewTransactionData(
   102  		baseSnapshotTime+3,
   103  		state.DefaultParameters())
   104  	require.NoError(t, err)
   105  
   106  	// Commit a bunch of unrelated transactions.
   107  
   108  	testSetupTxn, err = block.NewTransactionData(
   109  		baseSnapshotTime+1,
   110  		state.DefaultParameters())
   111  	require.NoError(t, err)
   112  
   113  	registerId2 := flow.RegisterID{
   114  		Owner: "",
   115  		Key:   "key2",
   116  	}
   117  	expectedValue2 := flow.RegisterValue([]byte("value2"))
   118  
   119  	err = testSetupTxn.Set(registerId2, expectedValue2)
   120  	require.NoError(t, err)
   121  
   122  	err = testSetupTxn.Finalize()
   123  	require.NoError(t, err)
   124  
   125  	_, err = testSetupTxn.Commit()
   126  	require.NoError(t, err)
   127  
   128  	testSetupTxn, err = block.NewTransactionData(
   129  		baseSnapshotTime+2,
   130  		state.DefaultParameters())
   131  	require.NoError(t, err)
   132  
   133  	registerId3 := flow.RegisterID{
   134  		Owner: "",
   135  		Key:   "key3",
   136  	}
   137  	expectedValue3 := flow.RegisterValue([]byte("value3"))
   138  
   139  	err = testSetupTxn.Set(registerId3, expectedValue3)
   140  	require.NoError(t, err)
   141  
   142  	err = testSetupTxn.Finalize()
   143  	require.NoError(t, err)
   144  
   145  	_, err = testSetupTxn.Commit()
   146  	require.NoError(t, err)
   147  
   148  	// Actual test
   149  
   150  	_, err = testTxn.Get(registerId1)
   151  	require.NoError(t, err)
   152  
   153  	if shouldFinalize {
   154  		err = testTxn.Finalize()
   155  		require.NoError(t, err)
   156  
   157  		require.NotNil(t, testTxn.finalizedExecutionSnapshot)
   158  	} else {
   159  		require.Nil(t, testTxn.finalizedExecutionSnapshot)
   160  	}
   161  
   162  	// Check the original snapshot tree before calling validate.
   163  	require.Equal(t, baseSnapshotTime+1, testTxn.SnapshotTime())
   164  
   165  	value, err = testTxn.snapshot.Get(registerId1)
   166  	require.NoError(t, err)
   167  	require.Equal(t, expectedValue1, value)
   168  
   169  	value, err = testTxn.snapshot.Get(registerId2)
   170  	require.NoError(t, err)
   171  	require.Nil(t, value)
   172  
   173  	value, err = testTxn.snapshot.Get(registerId3)
   174  	require.NoError(t, err)
   175  	require.Nil(t, value)
   176  
   177  	// Validate should not detect any conflict and should rebase the snapshot.
   178  	err = testTxn.Validate()
   179  	require.NoError(t, err)
   180  
   181  	// Ensure validate rebase to a new snapshot tree.
   182  	require.Equal(t, baseSnapshotTime+3, testTxn.SnapshotTime())
   183  
   184  	value, err = testTxn.snapshot.Get(registerId1)
   185  	require.NoError(t, err)
   186  	require.Equal(t, expectedValue1, value)
   187  
   188  	value, err = testTxn.snapshot.Get(registerId2)
   189  	require.NoError(t, err)
   190  	require.Equal(t, expectedValue2, value)
   191  
   192  	value, err = testTxn.snapshot.Get(registerId3)
   193  	require.NoError(t, err)
   194  	require.Equal(t, expectedValue3, value)
   195  
   196  	// Note: we can't make additional Get calls on a finalized transaction.
   197  	if shouldFinalize {
   198  		_, err = testTxn.Get(registerId1)
   199  		require.ErrorContains(t, err, "cannot Get on a finalized state")
   200  
   201  		_, err = testTxn.Get(registerId2)
   202  		require.ErrorContains(t, err, "cannot Get on a finalized state")
   203  
   204  		_, err = testTxn.Get(registerId3)
   205  		require.ErrorContains(t, err, "cannot Get on a finalized state")
   206  	} else {
   207  		value, err = testTxn.Get(registerId1)
   208  		require.NoError(t, err)
   209  		require.Equal(t, expectedValue1, value)
   210  
   211  		value, err = testTxn.Get(registerId2)
   212  		require.NoError(t, err)
   213  		require.Equal(t, expectedValue2, value)
   214  
   215  		value, err = testTxn.Get(registerId3)
   216  		require.NoError(t, err)
   217  		require.Equal(t, expectedValue3, value)
   218  	}
   219  }
   220  
   221  func TestBlockDataValidateInterim(t *testing.T) {
   222  	testBlockDataValidate(t, false)
   223  }
   224  
   225  func TestBlockDataValidateFinalized(t *testing.T) {
   226  	testBlockDataValidate(t, true)
   227  }
   228  
   229  func testBlockDataValidateRejectConflict(
   230  	t *testing.T,
   231  	shouldFinalize bool,
   232  	conflictTxn int, // [1, 2, 3]
   233  ) {
   234  	baseSnapshotTime := logical.Time(32)
   235  	block := NewBlockData(nil, baseSnapshotTime)
   236  
   237  	// Commit a bunch of unrelated updates
   238  
   239  	for ; baseSnapshotTime < 42; baseSnapshotTime++ {
   240  		testSetupTxn, err := block.NewTransactionData(
   241  			baseSnapshotTime,
   242  			state.DefaultParameters())
   243  		require.NoError(t, err)
   244  
   245  		err = testSetupTxn.Set(
   246  			flow.RegisterID{
   247  				Owner: "",
   248  				Key:   fmt.Sprintf("other key - %d", baseSnapshotTime),
   249  			},
   250  			[]byte("blah"))
   251  		require.NoError(t, err)
   252  
   253  		err = testSetupTxn.Finalize()
   254  		require.NoError(t, err)
   255  
   256  		_, err = testSetupTxn.Commit()
   257  		require.NoError(t, err)
   258  	}
   259  
   260  	// Start the test transaction at an "older" snapshot to ensure valdiate
   261  	// works as expected.
   262  
   263  	testTxnTime := baseSnapshotTime + 3
   264  	testTxn, err := block.NewTransactionData(
   265  		testTxnTime,
   266  		state.DefaultParameters())
   267  	require.NoError(t, err)
   268  
   269  	// Commit one key per test setup transaction.  One of these keys will
   270  	// conflicts with the test txn.
   271  
   272  	txn1Time := baseSnapshotTime
   273  	testSetupTxn, err := block.NewTransactionData(
   274  		txn1Time,
   275  		state.DefaultParameters())
   276  	require.NoError(t, err)
   277  
   278  	registerId1 := flow.RegisterID{
   279  		Owner: "",
   280  		Key:   "key1",
   281  	}
   282  
   283  	err = testSetupTxn.Set(registerId1, []byte("value1"))
   284  	require.NoError(t, err)
   285  
   286  	err = testSetupTxn.Finalize()
   287  	require.NoError(t, err)
   288  
   289  	_, err = testSetupTxn.Commit()
   290  	require.NoError(t, err)
   291  
   292  	txn2Time := baseSnapshotTime + 1
   293  	testSetupTxn, err = block.NewTransactionData(
   294  		txn2Time,
   295  		state.DefaultParameters())
   296  	require.NoError(t, err)
   297  
   298  	registerId2 := flow.RegisterID{
   299  		Owner: "",
   300  		Key:   "key2",
   301  	}
   302  
   303  	err = testSetupTxn.Set(registerId2, []byte("value2"))
   304  	require.NoError(t, err)
   305  
   306  	err = testSetupTxn.Finalize()
   307  	require.NoError(t, err)
   308  
   309  	_, err = testSetupTxn.Commit()
   310  	require.NoError(t, err)
   311  
   312  	txn3Time := baseSnapshotTime + 2
   313  	testSetupTxn, err = block.NewTransactionData(
   314  		txn3Time,
   315  		state.DefaultParameters())
   316  	require.NoError(t, err)
   317  
   318  	registerId3 := flow.RegisterID{
   319  		Owner: "",
   320  		Key:   "key3",
   321  	}
   322  
   323  	err = testSetupTxn.Set(registerId3, []byte("value3"))
   324  	require.NoError(t, err)
   325  
   326  	err = testSetupTxn.Finalize()
   327  	require.NoError(t, err)
   328  
   329  	_, err = testSetupTxn.Commit()
   330  	require.NoError(t, err)
   331  
   332  	// Actual test
   333  
   334  	var conflictTxnTime logical.Time
   335  	var conflictRegisterId flow.RegisterID
   336  	switch conflictTxn {
   337  	case 1:
   338  		conflictTxnTime = txn1Time
   339  		conflictRegisterId = registerId1
   340  	case 2:
   341  		conflictTxnTime = txn2Time
   342  		conflictRegisterId = registerId2
   343  	case 3:
   344  		conflictTxnTime = txn3Time
   345  		conflictRegisterId = registerId3
   346  	}
   347  
   348  	value, err := testTxn.Get(conflictRegisterId)
   349  	require.NoError(t, err)
   350  	require.Nil(t, value)
   351  
   352  	if shouldFinalize {
   353  		err = testTxn.Finalize()
   354  		require.NoError(t, err)
   355  
   356  		require.NotNil(t, testTxn.finalizedExecutionSnapshot)
   357  	} else {
   358  		require.Nil(t, testTxn.finalizedExecutionSnapshot)
   359  	}
   360  
   361  	// Check the original snapshot tree before calling validate.
   362  	require.Equal(t, baseSnapshotTime, testTxn.SnapshotTime())
   363  
   364  	err = testTxn.Validate()
   365  	require.ErrorContains(
   366  		t,
   367  		err,
   368  		fmt.Sprintf(
   369  			conflictErrorTemplate,
   370  			conflictTxnTime,
   371  			testTxnTime,
   372  			baseSnapshotTime,
   373  			conflictRegisterId))
   374  	require.True(t, errors.IsRetryableConflictError(err))
   375  
   376  	// Validate should not rebase the snapshot tree on error
   377  	require.Equal(t, baseSnapshotTime, testTxn.SnapshotTime())
   378  }
   379  
   380  func TestBlockDataValidateInterimRejectConflict(t *testing.T) {
   381  	testBlockDataValidateRejectConflict(t, false, 1)
   382  	testBlockDataValidateRejectConflict(t, false, 2)
   383  	testBlockDataValidateRejectConflict(t, false, 3)
   384  }
   385  
   386  func TestBlockDataValidateFinalizedRejectConflict(t *testing.T) {
   387  	testBlockDataValidateRejectConflict(t, true, 1)
   388  	testBlockDataValidateRejectConflict(t, true, 2)
   389  	testBlockDataValidateRejectConflict(t, true, 3)
   390  }
   391  
   392  func TestBlockDataCommit(t *testing.T) {
   393  	block := NewBlockData(nil, 0)
   394  
   395  	// Start test txn at an "older" snapshot.
   396  	txn, err := block.NewTransactionData(3, state.DefaultParameters())
   397  	require.NoError(t, err)
   398  
   399  	// Commit a bunch of unrelated updates
   400  
   401  	for i := logical.Time(0); i < 3; i++ {
   402  		testSetupTxn, err := block.NewTransactionData(
   403  			i,
   404  			state.DefaultParameters())
   405  		require.NoError(t, err)
   406  
   407  		err = testSetupTxn.Set(
   408  			flow.RegisterID{
   409  				Owner: "",
   410  				Key:   fmt.Sprintf("other key - %d", i),
   411  			},
   412  			[]byte("blah"))
   413  		require.NoError(t, err)
   414  
   415  		err = testSetupTxn.Finalize()
   416  		require.NoError(t, err)
   417  
   418  		_, err = testSetupTxn.Commit()
   419  		require.NoError(t, err)
   420  	}
   421  
   422  	// "resume" test txn
   423  
   424  	writeRegisterId := flow.RegisterID{
   425  		Owner: "",
   426  		Key:   "write",
   427  	}
   428  	expectedValue := flow.RegisterValue([]byte("value"))
   429  
   430  	err = txn.Set(writeRegisterId, expectedValue)
   431  	require.NoError(t, err)
   432  
   433  	readRegisterId := flow.RegisterID{
   434  		Owner: "",
   435  		Key:   "read",
   436  	}
   437  	value, err := txn.Get(readRegisterId)
   438  	require.NoError(t, err)
   439  	require.Nil(t, value)
   440  
   441  	err = txn.Finalize()
   442  	require.NoError(t, err)
   443  
   444  	// Actual test.  Ensure the transaction is committed.
   445  
   446  	require.Equal(t, logical.Time(0), txn.SnapshotTime())
   447  	require.Equal(t, logical.Time(3), block.LatestSnapshot().SnapshotTime())
   448  
   449  	executionSnapshot, err := txn.Commit()
   450  	require.NoError(t, err)
   451  	require.NotNil(t, executionSnapshot)
   452  	require.Equal(
   453  		t,
   454  		map[flow.RegisterID]struct{}{
   455  			readRegisterId: struct{}{},
   456  		},
   457  		executionSnapshot.ReadSet)
   458  	require.Equal(
   459  		t,
   460  		map[flow.RegisterID]flow.RegisterValue{
   461  			writeRegisterId: expectedValue,
   462  		},
   463  		executionSnapshot.WriteSet)
   464  
   465  	require.Equal(t, logical.Time(4), block.LatestSnapshot().SnapshotTime())
   466  
   467  	value, err = block.LatestSnapshot().Get(writeRegisterId)
   468  	require.NoError(t, err)
   469  	require.Equal(t, expectedValue, value)
   470  }
   471  
   472  func TestBlockDataCommitSnapshotReadDontAdvanceTime(t *testing.T) {
   473  	baseRegisterId := flow.RegisterID{
   474  		Owner: "",
   475  		Key:   "base",
   476  	}
   477  	baseValue := flow.RegisterValue([]byte("original"))
   478  
   479  	baseSnapshotTime := logical.Time(16)
   480  
   481  	block := NewBlockData(
   482  		snapshot.MapStorageSnapshot{
   483  			baseRegisterId: baseValue,
   484  		},
   485  		baseSnapshotTime)
   486  
   487  	txn := block.NewSnapshotReadTransactionData(state.DefaultParameters())
   488  
   489  	readRegisterId := flow.RegisterID{
   490  		Owner: "",
   491  		Key:   "read",
   492  	}
   493  	value, err := txn.Get(readRegisterId)
   494  	require.NoError(t, err)
   495  	require.Nil(t, value)
   496  
   497  	err = txn.Set(baseRegisterId, []byte("bad"))
   498  	require.NoError(t, err)
   499  
   500  	err = txn.Finalize()
   501  	require.NoError(t, err)
   502  
   503  	require.Equal(t, baseSnapshotTime, block.LatestSnapshot().SnapshotTime())
   504  
   505  	executionSnapshot, err := txn.Commit()
   506  	require.NoError(t, err)
   507  
   508  	require.NotNil(t, executionSnapshot)
   509  
   510  	require.Equal(
   511  		t,
   512  		map[flow.RegisterID]struct{}{
   513  			readRegisterId: struct{}{},
   514  		},
   515  		executionSnapshot.ReadSet)
   516  
   517  	// Ensure we have dropped the write set internally.
   518  	require.Nil(t, executionSnapshot.WriteSet)
   519  
   520  	// Ensure block snapshot is not updated.
   521  	require.Equal(t, baseSnapshotTime, block.LatestSnapshot().SnapshotTime())
   522  
   523  	value, err = block.LatestSnapshot().Get(baseRegisterId)
   524  	require.NoError(t, err)
   525  	require.Equal(t, baseValue, value)
   526  }
   527  
   528  func TestBlockDataCommitRejectNotFinalized(t *testing.T) {
   529  	block := NewBlockData(nil, 0)
   530  
   531  	txn, err := block.NewTransactionData(0, state.DefaultParameters())
   532  	require.NoError(t, err)
   533  
   534  	executionSnapshot, err := txn.Commit()
   535  	require.ErrorContains(t, err, "transaction not finalized")
   536  	require.False(t, errors.IsRetryableConflictError(err))
   537  	require.Nil(t, executionSnapshot)
   538  }
   539  
   540  func TestBlockDataCommitRejectConflict(t *testing.T) {
   541  	block := NewBlockData(nil, 0)
   542  
   543  	registerId := flow.RegisterID{
   544  		Owner: "",
   545  		Key:   "key1",
   546  	}
   547  
   548  	// Start test txn at an "older" snapshot.
   549  	testTxn, err := block.NewTransactionData(1, state.DefaultParameters())
   550  	require.NoError(t, err)
   551  
   552  	// Commit a conflicting key
   553  	testSetupTxn, err := block.NewTransactionData(0, state.DefaultParameters())
   554  	require.NoError(t, err)
   555  
   556  	err = testSetupTxn.Set(registerId, []byte("value"))
   557  	require.NoError(t, err)
   558  
   559  	err = testSetupTxn.Finalize()
   560  	require.NoError(t, err)
   561  
   562  	executionSnapshot, err := testSetupTxn.Commit()
   563  	require.NoError(t, err)
   564  	require.NotNil(t, executionSnapshot)
   565  
   566  	// Actual test
   567  
   568  	require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime())
   569  
   570  	value, err := testTxn.Get(registerId)
   571  	require.NoError(t, err)
   572  	require.Nil(t, value)
   573  
   574  	err = testTxn.Finalize()
   575  	require.NoError(t, err)
   576  
   577  	executionSnapshot, err = testTxn.Commit()
   578  	require.Error(t, err)
   579  	require.True(t, errors.IsRetryableConflictError(err))
   580  	require.Nil(t, executionSnapshot)
   581  
   582  	// testTxn is not committed to block.
   583  	require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime())
   584  }
   585  
   586  func TestBlockDataCommitRejectCommitGap(t *testing.T) {
   587  	block := NewBlockData(nil, 1)
   588  
   589  	for i := logical.Time(2); i < 5; i++ {
   590  		txn, err := block.NewTransactionData(i, state.DefaultParameters())
   591  		require.NoError(t, err)
   592  
   593  		err = txn.Finalize()
   594  		require.NoError(t, err)
   595  
   596  		executionSnapshot, err := txn.Commit()
   597  		require.ErrorContains(
   598  			t,
   599  			err,
   600  			fmt.Sprintf("missing commit range [1, %d)", i))
   601  		require.False(t, errors.IsRetryableConflictError(err))
   602  		require.Nil(t, executionSnapshot)
   603  
   604  		// testTxn is not committed to block.
   605  		require.Equal(t, logical.Time(1), block.LatestSnapshot().SnapshotTime())
   606  	}
   607  }
   608  
   609  func TestBlockDataCommitRejectNonIncreasingExecutionTime1(t *testing.T) {
   610  	block := NewBlockData(nil, 0)
   611  
   612  	testTxn, err := block.NewTransactionData(5, state.DefaultParameters())
   613  	require.NoError(t, err)
   614  
   615  	err = testTxn.Finalize()
   616  	require.NoError(t, err)
   617  
   618  	// Commit a bunch of unrelated transactions.
   619  	for i := logical.Time(0); i < 10; i++ {
   620  		txn, err := block.NewTransactionData(i, state.DefaultParameters())
   621  		require.NoError(t, err)
   622  
   623  		err = txn.Finalize()
   624  		require.NoError(t, err)
   625  
   626  		_, err = txn.Commit()
   627  		require.NoError(t, err)
   628  	}
   629  
   630  	// sanity check before testing commit.
   631  	require.Equal(t, logical.Time(10), block.LatestSnapshot().SnapshotTime())
   632  
   633  	// "re-commit" an already committed transaction
   634  	executionSnapshot, err := testTxn.Commit()
   635  	require.ErrorContains(t, err, "non-increasing time (9 >= 5)")
   636  	require.False(t, errors.IsRetryableConflictError(err))
   637  	require.Nil(t, executionSnapshot)
   638  
   639  	// testTxn is not committed to block.
   640  	require.Equal(t, logical.Time(10), block.LatestSnapshot().SnapshotTime())
   641  }
   642  
   643  func TestBlockDataCommitRejectNonIncreasingExecutionTime2(t *testing.T) {
   644  	block := NewBlockData(nil, 13)
   645  
   646  	testTxn, err := block.NewTransactionData(13, state.DefaultParameters())
   647  	require.NoError(t, err)
   648  
   649  	err = testTxn.Finalize()
   650  	require.NoError(t, err)
   651  
   652  	executionSnapshot, err := testTxn.Commit()
   653  	require.NoError(t, err)
   654  	require.NotNil(t, executionSnapshot)
   655  
   656  	// "re-commit" an already committed transaction
   657  	executionSnapshot, err = testTxn.Commit()
   658  	require.ErrorContains(t, err, "non-increasing time (13 >= 13)")
   659  	require.False(t, errors.IsRetryableConflictError(err))
   660  	require.Nil(t, executionSnapshot)
   661  }