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

     1  package state_test
     2  
     3  import (
     4  	"math"
     5  	"testing"
     6  
     7  	"github.com/onflow/cadence/runtime/common"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/fvm/meter"
    11  	"github.com/onflow/flow-go/fvm/storage/state"
    12  	"github.com/onflow/flow-go/model/flow"
    13  	"github.com/onflow/flow-go/utils/unittest"
    14  )
    15  
    16  func newTestTransactionState() state.NestedTransactionPreparer {
    17  	return state.NewTransactionState(
    18  		nil,
    19  		state.DefaultParameters(),
    20  	)
    21  }
    22  
    23  func TestUnrestrictedNestedTransactionBasic(t *testing.T) {
    24  	txn := newTestTransactionState()
    25  
    26  	mainState := txn.MainTransactionId().StateForTestingOnly()
    27  
    28  	require.Equal(t, 0, txn.NumNestedTransactions())
    29  	require.False(t, txn.IsParseRestricted())
    30  
    31  	id1, err := txn.BeginNestedTransaction()
    32  	require.NoError(t, err)
    33  
    34  	require.Equal(t, 1, txn.NumNestedTransactions())
    35  	require.False(t, txn.IsParseRestricted())
    36  
    37  	require.True(t, txn.IsCurrent(id1))
    38  
    39  	nestedState1 := id1.StateForTestingOnly()
    40  
    41  	id2, err := txn.BeginNestedTransaction()
    42  	require.NoError(t, err)
    43  
    44  	require.Equal(t, 2, txn.NumNestedTransactions())
    45  	require.False(t, txn.IsParseRestricted())
    46  
    47  	require.False(t, txn.IsCurrent(id1))
    48  	require.True(t, txn.IsCurrent(id2))
    49  
    50  	nestedState2 := id2.StateForTestingOnly()
    51  
    52  	// Ensure the values are written to the correctly nested state
    53  
    54  	key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key")
    55  	val := createByteArray(2)
    56  
    57  	err = txn.Set(key, val)
    58  	require.NoError(t, err)
    59  
    60  	v, err := nestedState2.Get(key)
    61  	require.NoError(t, err)
    62  	require.Equal(t, val, v)
    63  
    64  	v, err = nestedState1.Get(key)
    65  	require.NoError(t, err)
    66  	require.Nil(t, v)
    67  
    68  	v, err = mainState.Get(key)
    69  	require.NoError(t, err)
    70  	require.Nil(t, v)
    71  
    72  	// Ensure nested transactions are merged correctly
    73  
    74  	_, err = txn.CommitNestedTransaction(id2)
    75  	require.NoError(t, err)
    76  
    77  	require.Equal(t, 1, txn.NumNestedTransactions())
    78  	require.True(t, txn.IsCurrent(id1))
    79  
    80  	v, err = nestedState1.Get(key)
    81  	require.NoError(t, err)
    82  	require.Equal(t, val, v)
    83  
    84  	v, err = mainState.Get(key)
    85  	require.NoError(t, err)
    86  	require.Nil(t, v)
    87  
    88  	_, err = txn.CommitNestedTransaction(id1)
    89  	require.NoError(t, err)
    90  
    91  	require.Equal(t, 0, txn.NumNestedTransactions())
    92  	require.True(t, txn.IsCurrent(txn.MainTransactionId()))
    93  
    94  	v, err = mainState.Get(key)
    95  	require.NoError(t, err)
    96  	require.Equal(t, val, v)
    97  }
    98  
    99  func TestUnrestrictedNestedTransactionDifferentMeterParams(t *testing.T) {
   100  	txn := newTestTransactionState()
   101  
   102  	mainState := txn.MainTransactionId().StateForTestingOnly()
   103  
   104  	require.Equal(t, uint(math.MaxUint), mainState.TotalMemoryLimit())
   105  
   106  	id1, err := txn.BeginNestedTransactionWithMeterParams(
   107  		meter.DefaultParameters().WithMemoryLimit(1))
   108  	require.NoError(t, err)
   109  
   110  	nestedState1 := id1.StateForTestingOnly()
   111  
   112  	require.Equal(t, uint(1), nestedState1.TotalMemoryLimit())
   113  
   114  	id2, err := txn.BeginNestedTransactionWithMeterParams(
   115  		meter.DefaultParameters().WithMemoryLimit(2))
   116  	require.NoError(t, err)
   117  
   118  	nestedState2 := id2.StateForTestingOnly()
   119  
   120  	require.Equal(t, uint(2), nestedState2.TotalMemoryLimit())
   121  
   122  	// inherits memory limit from parent
   123  
   124  	id3, err := txn.BeginNestedTransaction()
   125  	require.NoError(t, err)
   126  
   127  	nestedState3 := id3.StateForTestingOnly()
   128  
   129  	require.Equal(t, uint(2), nestedState3.TotalMemoryLimit())
   130  }
   131  
   132  func TestParseRestrictedNestedTransactionBasic(t *testing.T) {
   133  	txn := newTestTransactionState()
   134  
   135  	mainId := txn.MainTransactionId()
   136  	mainState := mainId.StateForTestingOnly()
   137  
   138  	require.Equal(t, 0, txn.NumNestedTransactions())
   139  	require.False(t, txn.IsParseRestricted())
   140  
   141  	id1, err := txn.BeginNestedTransaction()
   142  	require.NoError(t, err)
   143  
   144  	require.Equal(t, 1, txn.NumNestedTransactions())
   145  	require.False(t, txn.IsParseRestricted())
   146  
   147  	nestedState := id1.StateForTestingOnly()
   148  
   149  	loc1 := common.AddressLocation{
   150  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   151  		Name:    "loc1",
   152  	}
   153  
   154  	restrictedId1, err := txn.BeginParseRestrictedNestedTransaction(loc1)
   155  	require.NoError(t, err)
   156  
   157  	require.Equal(t, 2, txn.NumNestedTransactions())
   158  	require.True(t, txn.IsParseRestricted())
   159  
   160  	restrictedNestedState1 := restrictedId1.StateForTestingOnly()
   161  
   162  	loc2 := common.AddressLocation{
   163  		Address: common.MustBytesToAddress([]byte{2, 2, 2}),
   164  		Name:    "loc2",
   165  	}
   166  
   167  	restrictedId2, err := txn.BeginParseRestrictedNestedTransaction(loc2)
   168  	require.NoError(t, err)
   169  
   170  	require.Equal(t, 3, txn.NumNestedTransactions())
   171  	require.True(t, txn.IsParseRestricted())
   172  
   173  	restrictedNestedState2 := restrictedId2.StateForTestingOnly()
   174  
   175  	// Sanity check
   176  
   177  	key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key")
   178  
   179  	v, err := restrictedNestedState2.Get(key)
   180  	require.NoError(t, err)
   181  	require.Nil(t, v)
   182  
   183  	v, err = restrictedNestedState1.Get(key)
   184  	require.NoError(t, err)
   185  	require.Nil(t, v)
   186  
   187  	v, err = nestedState.Get(key)
   188  	require.NoError(t, err)
   189  	require.Nil(t, v)
   190  
   191  	v, err = mainState.Get(key)
   192  	require.NoError(t, err)
   193  	require.Nil(t, v)
   194  
   195  	// Ensures attaching and committing cached nested transaction works
   196  
   197  	val := createByteArray(2)
   198  
   199  	cachedState := state.NewExecutionState(
   200  		nil,
   201  		state.DefaultParameters(),
   202  	)
   203  
   204  	err = cachedState.Set(key, val)
   205  	require.NoError(t, err)
   206  
   207  	err = txn.AttachAndCommitNestedTransaction(cachedState.Finalize())
   208  	require.NoError(t, err)
   209  
   210  	require.Equal(t, 3, txn.NumNestedTransactions())
   211  	require.True(t, txn.IsCurrent(restrictedId2))
   212  
   213  	v, err = restrictedNestedState2.Get(key)
   214  	require.NoError(t, err)
   215  	require.Equal(t, val, v)
   216  
   217  	v, err = restrictedNestedState1.Get(key)
   218  	require.NoError(t, err)
   219  	require.Nil(t, v)
   220  
   221  	v, err = nestedState.Get(key)
   222  	require.NoError(t, err)
   223  	require.Nil(t, v)
   224  
   225  	v, err = mainState.Get(key)
   226  	require.NoError(t, err)
   227  	require.Nil(t, v)
   228  
   229  	// Ensure nested transactions are merged correctly
   230  
   231  	snapshot, err := txn.CommitParseRestrictedNestedTransaction(loc2)
   232  	require.NoError(t, err)
   233  	require.Equal(t, restrictedNestedState2.Finalize(), snapshot)
   234  
   235  	require.Equal(t, 2, txn.NumNestedTransactions())
   236  	require.True(t, txn.IsCurrent(restrictedId1))
   237  
   238  	v, err = restrictedNestedState1.Get(key)
   239  	require.NoError(t, err)
   240  	require.Equal(t, val, v)
   241  
   242  	v, err = nestedState.Get(key)
   243  	require.NoError(t, err)
   244  	require.Nil(t, v)
   245  
   246  	v, err = mainState.Get(key)
   247  	require.NoError(t, err)
   248  	require.Nil(t, v)
   249  
   250  	snapshot, err = txn.CommitParseRestrictedNestedTransaction(loc1)
   251  	require.NoError(t, err)
   252  	require.Equal(t, restrictedNestedState1.Finalize(), snapshot)
   253  
   254  	require.Equal(t, 1, txn.NumNestedTransactions())
   255  	require.True(t, txn.IsCurrent(id1))
   256  
   257  	v, err = nestedState.Get(key)
   258  	require.NoError(t, err)
   259  	require.Equal(t, val, v)
   260  
   261  	v, err = mainState.Get(key)
   262  	require.NoError(t, err)
   263  	require.Nil(t, v)
   264  
   265  	_, err = txn.CommitNestedTransaction(id1)
   266  	require.NoError(t, err)
   267  
   268  	require.Equal(t, 0, txn.NumNestedTransactions())
   269  	require.True(t, txn.IsCurrent(mainId))
   270  
   271  	v, err = mainState.Get(key)
   272  	require.NoError(t, err)
   273  	require.Equal(t, val, v)
   274  }
   275  
   276  func TestRestartNestedTransaction(t *testing.T) {
   277  	txn := newTestTransactionState()
   278  
   279  	require.Equal(t, 0, txn.NumNestedTransactions())
   280  
   281  	id, err := txn.BeginNestedTransaction()
   282  	require.NoError(t, err)
   283  
   284  	key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key")
   285  	val := createByteArray(2)
   286  
   287  	for i := 0; i < 10; i++ {
   288  		_, err := txn.BeginNestedTransaction()
   289  		require.NoError(t, err)
   290  
   291  		err = txn.Set(key, val)
   292  		require.NoError(t, err)
   293  	}
   294  
   295  	loc := common.AddressLocation{
   296  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   297  		Name:    "loc",
   298  	}
   299  
   300  	for i := 0; i < 5; i++ {
   301  		_, err := txn.BeginParseRestrictedNestedTransaction(loc)
   302  		require.NoError(t, err)
   303  
   304  		err = txn.Set(key, val)
   305  		require.NoError(t, err)
   306  	}
   307  
   308  	require.Equal(t, 16, txn.NumNestedTransactions())
   309  
   310  	state := id.StateForTestingOnly()
   311  	require.Equal(t, uint64(0), state.InteractionUsed())
   312  
   313  	// Restart will merge the meter stat, but not the register updates
   314  
   315  	err = txn.RestartNestedTransaction(id)
   316  	require.NoError(t, err)
   317  
   318  	require.Equal(t, 1, txn.NumNestedTransactions())
   319  	require.True(t, txn.IsCurrent(id))
   320  
   321  	require.Greater(t, state.InteractionUsed(), uint64(0))
   322  
   323  	v, err := state.Get(key)
   324  	require.NoError(t, err)
   325  	require.Nil(t, v)
   326  }
   327  
   328  func TestRestartNestedTransactionWithInvalidId(t *testing.T) {
   329  	txn := newTestTransactionState()
   330  
   331  	require.Equal(t, 0, txn.NumNestedTransactions())
   332  
   333  	id, err := txn.BeginNestedTransaction()
   334  	require.NoError(t, err)
   335  
   336  	key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key")
   337  	val := createByteArray(2)
   338  
   339  	err = txn.Set(key, val)
   340  	require.NoError(t, err)
   341  
   342  	var otherId state.NestedTransactionId
   343  	for i := 0; i < 10; i++ {
   344  		otherId, err = txn.BeginNestedTransaction()
   345  		require.NoError(t, err)
   346  
   347  		_, err = txn.CommitNestedTransaction(otherId)
   348  		require.NoError(t, err)
   349  	}
   350  
   351  	require.True(t, txn.IsCurrent(id))
   352  
   353  	err = txn.RestartNestedTransaction(otherId)
   354  	require.Error(t, err)
   355  
   356  	require.True(t, txn.IsCurrent(id))
   357  
   358  	v, err := txn.Get(key)
   359  	require.NoError(t, err)
   360  	require.Equal(t, val, v)
   361  }
   362  
   363  func TestUnrestrictedCannotCommitParseRestrictedNestedTransaction(t *testing.T) {
   364  	txn := newTestTransactionState()
   365  
   366  	loc := common.AddressLocation{
   367  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   368  		Name:    "loc",
   369  	}
   370  
   371  	id, err := txn.BeginNestedTransaction()
   372  	require.NoError(t, err)
   373  
   374  	require.Equal(t, 1, txn.NumNestedTransactions())
   375  	require.False(t, txn.IsParseRestricted())
   376  
   377  	_, err = txn.CommitParseRestrictedNestedTransaction(loc)
   378  	require.Error(t, err)
   379  
   380  	require.Equal(t, 1, txn.NumNestedTransactions())
   381  	require.True(t, txn.IsCurrent(id))
   382  }
   383  
   384  func TestUnrestrictedCannotCommitMainTransaction(t *testing.T) {
   385  	txn := newTestTransactionState()
   386  
   387  	id1, err := txn.BeginNestedTransaction()
   388  	require.NoError(t, err)
   389  
   390  	id2, err := txn.BeginNestedTransaction()
   391  	require.NoError(t, err)
   392  
   393  	require.Equal(t, 2, txn.NumNestedTransactions())
   394  
   395  	_, err = txn.CommitNestedTransaction(id1)
   396  	require.Error(t, err)
   397  
   398  	require.Equal(t, 2, txn.NumNestedTransactions())
   399  	require.True(t, txn.IsCurrent(id2))
   400  }
   401  
   402  func TestUnrestrictedCannotCommitUnexpectedNested(t *testing.T) {
   403  	txn := newTestTransactionState()
   404  
   405  	mainId := txn.MainTransactionId()
   406  
   407  	require.Equal(t, 0, txn.NumNestedTransactions())
   408  
   409  	_, err := txn.CommitNestedTransaction(mainId)
   410  	require.Error(t, err)
   411  
   412  	require.Equal(t, 0, txn.NumNestedTransactions())
   413  	require.True(t, txn.IsCurrent(mainId))
   414  }
   415  
   416  func TestParseRestrictedCannotBeginUnrestrictedNestedTransaction(t *testing.T) {
   417  	txn := newTestTransactionState()
   418  
   419  	loc := common.AddressLocation{
   420  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   421  		Name:    "loc",
   422  	}
   423  
   424  	id1, err := txn.BeginParseRestrictedNestedTransaction(loc)
   425  	require.NoError(t, err)
   426  
   427  	require.Equal(t, 1, txn.NumNestedTransactions())
   428  
   429  	id2, err := txn.BeginNestedTransaction()
   430  	require.Error(t, err)
   431  
   432  	require.Equal(t, 1, txn.NumNestedTransactions())
   433  	require.True(t, txn.IsCurrent(id1))
   434  	require.False(t, txn.IsCurrent(id2))
   435  }
   436  
   437  func TestParseRestrictedCannotCommitUnrestricted(t *testing.T) {
   438  	txn := newTestTransactionState()
   439  
   440  	loc := common.AddressLocation{
   441  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   442  		Name:    "loc",
   443  	}
   444  
   445  	id, err := txn.BeginParseRestrictedNestedTransaction(loc)
   446  	require.NoError(t, err)
   447  
   448  	require.Equal(t, 1, txn.NumNestedTransactions())
   449  
   450  	_, err = txn.CommitNestedTransaction(id)
   451  	require.Error(t, err)
   452  
   453  	require.Equal(t, 1, txn.NumNestedTransactions())
   454  	require.True(t, txn.IsCurrent(id))
   455  }
   456  
   457  func TestParseRestrictedCannotCommitLocationMismatch(t *testing.T) {
   458  	txn := newTestTransactionState()
   459  
   460  	loc := common.AddressLocation{
   461  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   462  		Name:    "loc",
   463  	}
   464  
   465  	id, err := txn.BeginParseRestrictedNestedTransaction(loc)
   466  	require.NoError(t, err)
   467  
   468  	require.Equal(t, 1, txn.NumNestedTransactions())
   469  
   470  	other := common.AddressLocation{
   471  		Address: common.MustBytesToAddress([]byte{1, 1, 1}),
   472  		Name:    "other",
   473  	}
   474  
   475  	cacheableSnapshot, err := txn.CommitParseRestrictedNestedTransaction(other)
   476  	require.Error(t, err)
   477  	require.Nil(t, cacheableSnapshot)
   478  
   479  	require.Equal(t, 1, txn.NumNestedTransactions())
   480  	require.True(t, txn.IsCurrent(id))
   481  }
   482  
   483  func TestFinalizeMainTransactionFailWithUnexpectedNestedTransactions(
   484  	t *testing.T,
   485  ) {
   486  	txn := newTestTransactionState()
   487  
   488  	_, err := txn.BeginNestedTransaction()
   489  	require.NoError(t, err)
   490  
   491  	executionSnapshot, err := txn.FinalizeMainTransaction()
   492  	require.Error(t, err)
   493  	require.Nil(t, executionSnapshot)
   494  }
   495  
   496  func TestFinalizeMainTransaction(t *testing.T) {
   497  	txn := newTestTransactionState()
   498  
   499  	id1, err := txn.BeginNestedTransaction()
   500  	require.NoError(t, err)
   501  
   502  	registerId := flow.NewRegisterID(unittest.RandomAddressFixture(), "bar")
   503  
   504  	value, err := txn.Get(registerId)
   505  	require.NoError(t, err)
   506  	require.Nil(t, value)
   507  
   508  	_, err = txn.CommitNestedTransaction(id1)
   509  	require.NoError(t, err)
   510  
   511  	value, err = txn.Get(registerId)
   512  	require.NoError(t, err)
   513  	require.Nil(t, value)
   514  
   515  	executionSnapshot, err := txn.FinalizeMainTransaction()
   516  	require.NoError(t, err)
   517  
   518  	require.Equal(
   519  		t,
   520  		executionSnapshot.ReadSet,
   521  		map[flow.RegisterID]struct{}{
   522  			registerId: struct{}{},
   523  		})
   524  
   525  	// Sanity check state is no longer accessible after FinalizeMainTransaction.
   526  	_, err = txn.Get(registerId)
   527  	require.ErrorContains(t, err, "cannot Get on a finalized state")
   528  }
   529  
   530  func TestInterimReadSet(t *testing.T) {
   531  	txn := newTestTransactionState()
   532  
   533  	// Setup test with a bunch of outstanding nested transaction.
   534  
   535  	readOwner := unittest.RandomAddressFixture()
   536  	writeOwner := unittest.RandomAddressFixture()
   537  
   538  	readRegisterId1 := flow.NewRegisterID(readOwner, "1")
   539  	readRegisterId2 := flow.NewRegisterID(readOwner, "2")
   540  	readRegisterId3 := flow.NewRegisterID(readOwner, "3")
   541  	readRegisterId4 := flow.NewRegisterID(readOwner, "4")
   542  
   543  	writeRegisterId1 := flow.NewRegisterID(writeOwner, "1")
   544  	writeValue1 := flow.RegisterValue([]byte("value1"))
   545  
   546  	writeRegisterId2 := flow.NewRegisterID(writeOwner, "2")
   547  	writeValue2 := flow.RegisterValue([]byte("value2"))
   548  
   549  	writeRegisterId3 := flow.NewRegisterID(writeOwner, "3")
   550  	writeValue3 := flow.RegisterValue([]byte("value3"))
   551  
   552  	err := txn.Set(writeRegisterId1, writeValue1)
   553  	require.NoError(t, err)
   554  
   555  	_, err = txn.Get(readRegisterId1)
   556  	require.NoError(t, err)
   557  
   558  	_, err = txn.Get(readRegisterId2)
   559  	require.NoError(t, err)
   560  
   561  	value, err := txn.Get(writeRegisterId1)
   562  	require.NoError(t, err)
   563  	require.Equal(t, writeValue1, value)
   564  
   565  	_, err = txn.BeginNestedTransaction()
   566  	require.NoError(t, err)
   567  
   568  	err = txn.Set(readRegisterId2, []byte("blah"))
   569  	require.NoError(t, err)
   570  
   571  	_, err = txn.Get(readRegisterId3)
   572  	require.NoError(t, err)
   573  
   574  	value, err = txn.Get(writeRegisterId1)
   575  	require.NoError(t, err)
   576  	require.Equal(t, writeValue1, value)
   577  
   578  	err = txn.Set(writeRegisterId2, writeValue2)
   579  	require.NoError(t, err)
   580  
   581  	_, err = txn.BeginNestedTransaction()
   582  	require.NoError(t, err)
   583  
   584  	err = txn.Set(writeRegisterId3, writeValue3)
   585  	require.NoError(t, err)
   586  
   587  	value, err = txn.Get(writeRegisterId1)
   588  	require.NoError(t, err)
   589  	require.Equal(t, writeValue1, value)
   590  
   591  	value, err = txn.Get(writeRegisterId2)
   592  	require.NoError(t, err)
   593  	require.Equal(t, writeValue2, value)
   594  
   595  	value, err = txn.Get(writeRegisterId3)
   596  	require.NoError(t, err)
   597  	require.Equal(t, writeValue3, value)
   598  
   599  	_, err = txn.Get(readRegisterId4)
   600  	require.NoError(t, err)
   601  
   602  	// Actual test
   603  
   604  	require.Equal(
   605  		t,
   606  		map[flow.RegisterID]struct{}{
   607  			readRegisterId1: struct{}{},
   608  			readRegisterId2: struct{}{},
   609  			readRegisterId3: struct{}{},
   610  			readRegisterId4: struct{}{},
   611  		},
   612  		txn.InterimReadSet())
   613  }