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

     1  package derived
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  
    10  	"github.com/onflow/flow-go/fvm/storage/errors"
    11  	"github.com/onflow/flow-go/fvm/storage/logical"
    12  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    13  	"github.com/onflow/flow-go/fvm/storage/state"
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/utils/unittest"
    16  )
    17  
    18  func newEmptyTestBlock() *DerivedDataTable[string, *string] {
    19  	return NewEmptyTable[string, *string](0)
    20  }
    21  
    22  func TestDerivedDataTableWithTransactionOffset(t *testing.T) {
    23  	block := NewEmptyTable[string, *string](18)
    24  
    25  	require.Equal(
    26  		t,
    27  		logical.Time(17),
    28  		block.LatestCommitExecutionTimeForTestingOnly())
    29  }
    30  
    31  func TestDerivedDataTableNormalTransactionInvalidExecutionTimeBound(
    32  	t *testing.T,
    33  ) {
    34  	block := newEmptyTestBlock()
    35  
    36  	_, err := block.NewTableTransaction(-1, -1)
    37  	require.ErrorContains(t, err, "execution time out of bound")
    38  
    39  	_, err = block.NewTableTransaction(0, 0)
    40  	require.NoError(t, err)
    41  
    42  	_, err = block.NewTableTransaction(0, logical.EndOfBlockExecutionTime)
    43  	require.ErrorContains(t, err, "execution time out of bound")
    44  
    45  	_, err = block.NewTableTransaction(0, logical.EndOfBlockExecutionTime-1)
    46  	require.NoError(t, err)
    47  }
    48  
    49  func TestDerivedDataTableNormalTransactionInvalidSnapshotTime(t *testing.T) {
    50  	block := newEmptyTestBlock()
    51  
    52  	_, err := block.NewTableTransaction(10, 0)
    53  	require.ErrorContains(t, err, "snapshot > execution")
    54  
    55  	_, err = block.NewTableTransaction(10, 10)
    56  	require.NoError(t, err)
    57  
    58  	_, err = block.NewTableTransaction(999, 998)
    59  	require.ErrorContains(t, err, "snapshot > execution")
    60  
    61  	_, err = block.NewTableTransaction(999, 999)
    62  	require.NoError(t, err)
    63  }
    64  
    65  func TestDerivedDataTableToValidateTime(t *testing.T) {
    66  	block := NewEmptyTable[string, *string](8)
    67  	require.Equal(
    68  		t,
    69  		logical.Time(7),
    70  		block.LatestCommitExecutionTimeForTestingOnly())
    71  
    72  	testTxnSnapshotTime := logical.Time(5)
    73  
    74  	testTxn, err := block.NewTableTransaction(testTxnSnapshotTime, 20)
    75  	require.NoError(t, err)
    76  	require.Equal(
    77  		t,
    78  		testTxnSnapshotTime,
    79  		testTxn.ToValidateTimeForTestingOnly())
    80  
    81  	testTxn.SetForTestingOnly("key1", nil, nil)
    82  	require.Equal(
    83  		t,
    84  		testTxnSnapshotTime,
    85  		testTxn.ToValidateTimeForTestingOnly())
    86  
    87  	err = testTxn.Validate()
    88  	require.NoError(t, err)
    89  	require.Equal(
    90  		t,
    91  		logical.Time(8),
    92  		testTxn.ToValidateTimeForTestingOnly())
    93  
    94  	testSetupTxn, err := block.NewTableTransaction(8, 8)
    95  	require.NoError(t, err)
    96  
    97  	invalidator1 := &testInvalidator{invalidateName: "blah"}
    98  
    99  	testSetupTxn.AddInvalidator(invalidator1)
   100  	err = testSetupTxn.Commit()
   101  	require.NoError(t, err)
   102  
   103  	err = testTxn.Validate()
   104  	require.NoError(t, err)
   105  	require.Equal(
   106  		t,
   107  		logical.Time(9),
   108  		testTxn.ToValidateTimeForTestingOnly())
   109  
   110  	require.Equal(t, 1, invalidator1.callCount)
   111  
   112  	// Multiple transactions committed between validate calls
   113  
   114  	testSetupTxn, err = block.NewTableTransaction(6, 9)
   115  	require.NoError(t, err)
   116  
   117  	invalidator2 := &testInvalidator{invalidateName: "blah"}
   118  
   119  	testSetupTxn.AddInvalidator(invalidator2)
   120  	err = testSetupTxn.Commit()
   121  	require.NoError(t, err)
   122  
   123  	testSetupTxn, err = block.NewTableTransaction(8, 10)
   124  	require.NoError(t, err)
   125  
   126  	invalidator3 := &testInvalidator{invalidateName: "blah"}
   127  
   128  	testSetupTxn.AddInvalidator(invalidator3)
   129  	err = testSetupTxn.Commit()
   130  	require.NoError(t, err)
   131  
   132  	err = testTxn.Validate()
   133  	require.NoError(t, err)
   134  	require.Equal(
   135  		t,
   136  		logical.Time(11),
   137  		testTxn.ToValidateTimeForTestingOnly())
   138  
   139  	require.Equal(t, 1, invalidator1.callCount)
   140  	require.Equal(t, 1, invalidator2.callCount)
   141  	require.Equal(t, 1, invalidator3.callCount)
   142  
   143  	// No validate time advancement
   144  
   145  	err = testTxn.Validate()
   146  	require.NoError(t, err)
   147  	require.Equal(
   148  		t,
   149  		logical.Time(11),
   150  		testTxn.ToValidateTimeForTestingOnly())
   151  
   152  	require.Equal(t, 1, invalidator1.callCount)
   153  	require.Equal(t, 1, invalidator2.callCount)
   154  	require.Equal(t, 1, invalidator3.callCount)
   155  
   156  	// Setting a value derived from snapshot time will reset the validate time
   157  
   158  	testTxn.SetForTestingOnly("key2", nil, nil)
   159  	require.Equal(
   160  		t,
   161  		testTxnSnapshotTime,
   162  		testTxn.ToValidateTimeForTestingOnly())
   163  
   164  	err = testTxn.Validate()
   165  	require.NoError(t, err)
   166  	require.Equal(
   167  		t,
   168  		logical.Time(11),
   169  		testTxn.ToValidateTimeForTestingOnly())
   170  
   171  	// callCount = 3 because key1 is validated twice, key2 validated once.
   172  	require.Equal(t, 3, invalidator1.callCount)
   173  	require.Equal(t, 3, invalidator2.callCount)
   174  	require.Equal(t, 3, invalidator3.callCount)
   175  
   176  	// validate error does not advance validated time
   177  
   178  	testSetupTxn, err = block.NewTableTransaction(11, 11)
   179  	require.NoError(t, err)
   180  
   181  	invalidator4 := &testInvalidator{invalidateName: "blah"}
   182  
   183  	testSetupTxn.AddInvalidator(invalidator4)
   184  	err = testSetupTxn.Commit()
   185  	require.NoError(t, err)
   186  
   187  	testSetupTxn, err = block.NewTableTransaction(12, 12)
   188  	require.NoError(t, err)
   189  
   190  	invalidator5 := &testInvalidator{invalidateAll: true}
   191  
   192  	testSetupTxn.AddInvalidator(invalidator5)
   193  	err = testSetupTxn.Commit()
   194  	require.NoError(t, err)
   195  
   196  	for i := 1; i < 10; i++ {
   197  		err = testTxn.Validate()
   198  		require.Error(t, err)
   199  		require.Equal(
   200  			t,
   201  			logical.Time(11),
   202  			testTxn.ToValidateTimeForTestingOnly())
   203  
   204  		require.Equal(t, 3, invalidator1.callCount)
   205  		require.Equal(t, 3, invalidator2.callCount)
   206  		require.Equal(t, 3, invalidator3.callCount)
   207  		require.Equal(t, i, invalidator4.callCount)
   208  		require.Equal(t, i, invalidator5.callCount)
   209  	}
   210  }
   211  
   212  func TestDerivedDataTableOutOfOrderValidate(t *testing.T) {
   213  	block := newEmptyTestBlock()
   214  
   215  	testTxn1, err := block.NewTableTransaction(0, 0)
   216  	require.NoError(t, err)
   217  
   218  	testTxn2, err := block.NewTableTransaction(1, 1)
   219  	require.NoError(t, err)
   220  
   221  	testTxn3, err := block.NewTableTransaction(2, 2)
   222  	require.NoError(t, err)
   223  
   224  	testTxn4, err := block.NewTableTransaction(3, 3)
   225  	require.NoError(t, err)
   226  
   227  	// Validate can be called in any order as long as the transactions
   228  	// are committed in the correct order.
   229  
   230  	validateErr := testTxn4.Validate()
   231  	require.NoError(t, validateErr)
   232  
   233  	validateErr = testTxn2.Validate()
   234  	require.NoError(t, validateErr)
   235  
   236  	validateErr = testTxn3.Validate()
   237  	require.NoError(t, validateErr)
   238  
   239  	validateErr = testTxn1.Validate()
   240  	require.NoError(t, validateErr)
   241  
   242  	err = testTxn1.Commit()
   243  	require.NoError(t, err)
   244  
   245  	validateErr = testTxn2.Validate()
   246  	require.NoError(t, validateErr)
   247  
   248  	validateErr = testTxn3.Validate()
   249  	require.NoError(t, validateErr)
   250  
   251  	validateErr = testTxn4.Validate()
   252  	require.NoError(t, validateErr)
   253  
   254  	validateErr = testTxn2.Validate()
   255  	require.NoError(t, validateErr)
   256  }
   257  
   258  func TestDerivedDataTableValidateRejectOutOfOrderCommit(t *testing.T) {
   259  	block := newEmptyTestBlock()
   260  
   261  	testTxn, err := block.NewTableTransaction(0, 0)
   262  	require.NoError(t, err)
   263  
   264  	testSetupTxn, err := block.NewTableTransaction(0, 1)
   265  	require.NoError(t, err)
   266  
   267  	validateErr := testTxn.Validate()
   268  	require.NoError(t, validateErr)
   269  
   270  	err = testSetupTxn.Commit()
   271  	require.NoError(t, err)
   272  
   273  	validateErr = testTxn.Validate()
   274  	require.ErrorContains(t, validateErr, "non-increasing time")
   275  	require.False(t, errors.IsRetryableConflictError(validateErr))
   276  }
   277  
   278  func TestDerivedDataTableValidateRejectNonIncreasingExecutionTime(t *testing.T) {
   279  	block := newEmptyTestBlock()
   280  
   281  	testSetupTxn, err := block.NewTableTransaction(0, 0)
   282  	require.NoError(t, err)
   283  
   284  	err = testSetupTxn.Commit()
   285  	require.NoError(t, err)
   286  
   287  	testTxn, err := block.NewTableTransaction(0, 0)
   288  	require.NoError(t, err)
   289  
   290  	validateErr := testTxn.Validate()
   291  	require.ErrorContains(t, validateErr, "non-increasing time")
   292  	require.False(t, errors.IsRetryableConflictError(validateErr))
   293  }
   294  
   295  func TestDerivedDataTableValidateRejectOutdatedReadSet(t *testing.T) {
   296  	block := newEmptyTestBlock()
   297  
   298  	testSetupTxn1, err := block.NewTableTransaction(0, 0)
   299  	require.NoError(t, err)
   300  
   301  	testSetupTxn2, err := block.NewTableTransaction(0, 1)
   302  	require.NoError(t, err)
   303  
   304  	testTxn, err := block.NewTableTransaction(0, 2)
   305  	require.NoError(t, err)
   306  
   307  	key := "abc"
   308  	valueString := "value"
   309  	expectedValue := &valueString
   310  	expectedSnapshot := &snapshot.ExecutionSnapshot{}
   311  
   312  	testSetupTxn1.SetForTestingOnly(key, expectedValue, expectedSnapshot)
   313  
   314  	testSetupTxn1.AddInvalidator(&testInvalidator{})
   315  
   316  	err = testSetupTxn1.Commit()
   317  	require.NoError(t, err)
   318  
   319  	validateErr := testTxn.Validate()
   320  	require.NoError(t, validateErr)
   321  
   322  	actualProg, actualSnapshot, ok := testTxn.GetForTestingOnly(key)
   323  	require.True(t, ok)
   324  	require.Same(t, expectedValue, actualProg)
   325  	require.Same(t, expectedSnapshot, actualSnapshot)
   326  
   327  	validateErr = testTxn.Validate()
   328  	require.NoError(t, validateErr)
   329  
   330  	testSetupTxn2.AddInvalidator(&testInvalidator{invalidateAll: true})
   331  
   332  	err = testSetupTxn2.Commit()
   333  	require.NoError(t, err)
   334  
   335  	validateErr = testTxn.Validate()
   336  	require.ErrorContains(t, validateErr, "outdated read set")
   337  	require.True(t, errors.IsRetryableConflictError(validateErr))
   338  }
   339  
   340  func TestDerivedDataTableValidateRejectOutdatedWriteSet(t *testing.T) {
   341  	block := newEmptyTestBlock()
   342  
   343  	testSetupTxn, err := block.NewTableTransaction(0, 0)
   344  	require.NoError(t, err)
   345  
   346  	testSetupTxn.AddInvalidator(&testInvalidator{invalidateAll: true})
   347  
   348  	err = testSetupTxn.Commit()
   349  	require.NoError(t, err)
   350  
   351  	require.Equal(t, 1, len(block.InvalidatorsForTestingOnly()))
   352  
   353  	testTxn, err := block.NewTableTransaction(0, 1)
   354  	require.NoError(t, err)
   355  
   356  	value := "value"
   357  	testTxn.SetForTestingOnly("key", &value, &snapshot.ExecutionSnapshot{})
   358  
   359  	validateErr := testTxn.Validate()
   360  	require.ErrorContains(t, validateErr, "outdated write set")
   361  	require.True(t, errors.IsRetryableConflictError(validateErr))
   362  }
   363  
   364  func TestDerivedDataTableValidateIgnoreInvalidatorsOlderThanSnapshot(t *testing.T) {
   365  	block := newEmptyTestBlock()
   366  
   367  	testSetupTxn, err := block.NewTableTransaction(0, 0)
   368  	require.NoError(t, err)
   369  
   370  	testSetupTxn.AddInvalidator(&testInvalidator{invalidateAll: true})
   371  	err = testSetupTxn.Commit()
   372  	require.NoError(t, err)
   373  
   374  	require.Equal(t, 1, len(block.InvalidatorsForTestingOnly()))
   375  
   376  	testTxn, err := block.NewTableTransaction(1, 1)
   377  	require.NoError(t, err)
   378  
   379  	value := "value"
   380  	testTxn.SetForTestingOnly("key", &value, &snapshot.ExecutionSnapshot{})
   381  
   382  	err = testTxn.Validate()
   383  	require.NoError(t, err)
   384  }
   385  
   386  func TestDerivedDataTableCommitWriteOnlyTransactionNoInvalidation(t *testing.T) {
   387  	block := newEmptyTestBlock()
   388  
   389  	testTxn, err := block.NewTableTransaction(0, 0)
   390  	require.NoError(t, err)
   391  
   392  	key := "234"
   393  
   394  	actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key)
   395  	require.False(t, ok)
   396  	require.Nil(t, actualValue)
   397  	require.Nil(t, actualSnapshot)
   398  
   399  	valueString := "stuff"
   400  	expectedValue := &valueString
   401  	expectedSnapshot := &snapshot.ExecutionSnapshot{}
   402  
   403  	testTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot)
   404  
   405  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key)
   406  	require.True(t, ok)
   407  	require.Same(t, expectedValue, actualValue)
   408  	require.Same(t, expectedSnapshot, actualSnapshot)
   409  
   410  	testTxn.AddInvalidator(&testInvalidator{})
   411  
   412  	err = testTxn.Commit()
   413  	require.NoError(t, err)
   414  
   415  	// Sanity check
   416  
   417  	require.Equal(
   418  		t,
   419  		logical.Time(0),
   420  		block.LatestCommitExecutionTimeForTestingOnly())
   421  
   422  	require.Equal(t, 0, len(block.InvalidatorsForTestingOnly()))
   423  
   424  	entries := block.EntriesForTestingOnly()
   425  	require.Equal(t, 1, len(entries))
   426  
   427  	entry, ok := entries[key]
   428  	require.True(t, ok)
   429  	require.False(t, entry.isInvalid)
   430  	require.Same(t, expectedValue, entry.Value)
   431  	require.Same(t, expectedSnapshot, entry.ExecutionSnapshot)
   432  }
   433  
   434  func TestDerivedDataTableCommitWriteOnlyTransactionWithInvalidation(t *testing.T) {
   435  	block := newEmptyTestBlock()
   436  
   437  	testTxnTime := logical.Time(47)
   438  	testTxn, err := block.NewTableTransaction(0, testTxnTime)
   439  	require.NoError(t, err)
   440  
   441  	key := "999"
   442  
   443  	actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key)
   444  	require.False(t, ok)
   445  	require.Nil(t, actualValue)
   446  	require.Nil(t, actualSnapshot)
   447  
   448  	valueString := "blah"
   449  	expectedValue := &valueString
   450  	expectedSnapshot := &snapshot.ExecutionSnapshot{}
   451  
   452  	testTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot)
   453  
   454  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key)
   455  	require.True(t, ok)
   456  	require.Same(t, expectedValue, actualValue)
   457  	require.Same(t, expectedSnapshot, actualSnapshot)
   458  
   459  	invalidator := &testInvalidator{invalidateAll: true}
   460  
   461  	testTxn.AddInvalidator(invalidator)
   462  
   463  	err = testTxn.Commit()
   464  	require.NoError(t, err)
   465  
   466  	// Sanity check
   467  
   468  	require.Equal(
   469  		t,
   470  		testTxnTime,
   471  		block.LatestCommitExecutionTimeForTestingOnly())
   472  
   473  	require.Equal(
   474  		t,
   475  		chainedTableInvalidators[string, *string]{
   476  			{
   477  				TableInvalidator: invalidator,
   478  				executionTime:    testTxnTime,
   479  			},
   480  		},
   481  		block.InvalidatorsForTestingOnly())
   482  
   483  	require.Equal(t, 0, len(block.EntriesForTestingOnly()))
   484  }
   485  
   486  func TestDerivedDataTableCommitErrorOnDuplicateWriteEntries(t *testing.T) {
   487  	block := newEmptyTestBlock()
   488  
   489  	testSetupTxn, err := block.NewTableTransaction(0, 11)
   490  	require.NoError(t, err)
   491  
   492  	testTxn, err := block.NewTableTransaction(10, 12)
   493  	require.NoError(t, err)
   494  
   495  	key := "17"
   496  	valueString := "foo"
   497  	expectedValue := &valueString
   498  	expectedSnapshot := &snapshot.ExecutionSnapshot{}
   499  
   500  	testSetupTxn.SetForTestingOnly(key, expectedValue, expectedSnapshot)
   501  
   502  	err = testSetupTxn.Commit()
   503  	require.NoError(t, err)
   504  
   505  	entries := block.EntriesForTestingOnly()
   506  	require.Equal(t, 1, len(entries))
   507  
   508  	expectedEntry, ok := entries[key]
   509  	require.True(t, ok)
   510  
   511  	otherString := "other"
   512  	otherValue := &otherString
   513  	otherSnapshot := &snapshot.ExecutionSnapshot{}
   514  
   515  	testTxn.SetForTestingOnly(key, otherValue, otherSnapshot)
   516  
   517  	err = testTxn.Commit()
   518  
   519  	require.Error(t, err)
   520  	require.True(t, errors.IsRetryableConflictError(err))
   521  
   522  	entries = block.EntriesForTestingOnly()
   523  	require.Equal(t, 1, len(entries))
   524  
   525  	actualEntry, ok := entries[key]
   526  	require.True(t, ok)
   527  
   528  	require.Same(t, expectedEntry, actualEntry)
   529  }
   530  
   531  func TestDerivedDataTableCommitReadOnlyTransactionNoInvalidation(t *testing.T) {
   532  	block := newEmptyTestBlock()
   533  
   534  	testSetupTxn, err := block.NewTableTransaction(0, 0)
   535  	require.NoError(t, err)
   536  
   537  	testTxn, err := block.NewTableTransaction(0, 1)
   538  	require.NoError(t, err)
   539  
   540  	key1 := "key1"
   541  	valStr1 := "value1"
   542  	expectedValue1 := &valStr1
   543  	expectedSnapshot1 := &snapshot.ExecutionSnapshot{}
   544  
   545  	testSetupTxn.SetForTestingOnly(key1, expectedValue1, expectedSnapshot1)
   546  
   547  	key2 := "key2"
   548  	valStr2 := "value2"
   549  	expectedValue2 := &valStr2
   550  	expectedSnapshot2 := &snapshot.ExecutionSnapshot{}
   551  
   552  	testSetupTxn.SetForTestingOnly(key2, expectedValue2, expectedSnapshot2)
   553  
   554  	err = testSetupTxn.Commit()
   555  	require.NoError(t, err)
   556  
   557  	actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key1)
   558  	require.True(t, ok)
   559  	require.Same(t, expectedValue1, actualValue)
   560  	require.Same(t, expectedSnapshot1, actualSnapshot)
   561  
   562  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key2)
   563  	require.True(t, ok)
   564  	require.Same(t, expectedValue2, actualValue)
   565  	require.Same(t, expectedSnapshot2, actualSnapshot)
   566  
   567  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly("key3")
   568  	require.False(t, ok)
   569  	require.Nil(t, actualValue)
   570  	require.Nil(t, actualSnapshot)
   571  
   572  	testTxn.AddInvalidator(&testInvalidator{})
   573  
   574  	err = testTxn.Commit()
   575  	require.NoError(t, err)
   576  
   577  	// Sanity check
   578  
   579  	require.Equal(
   580  		t,
   581  		logical.Time(1),
   582  		block.LatestCommitExecutionTimeForTestingOnly())
   583  
   584  	require.Equal(t, 0, len(block.InvalidatorsForTestingOnly()))
   585  
   586  	entries := block.EntriesForTestingOnly()
   587  	require.Equal(t, 2, len(entries))
   588  
   589  	entry, ok := entries[key1]
   590  	require.True(t, ok)
   591  	require.False(t, entry.isInvalid)
   592  	require.Same(t, expectedValue1, entry.Value)
   593  	require.Same(t, expectedSnapshot1, entry.ExecutionSnapshot)
   594  
   595  	entry, ok = entries[key2]
   596  	require.True(t, ok)
   597  	require.False(t, entry.isInvalid)
   598  	require.Same(t, expectedValue2, entry.Value)
   599  	require.Same(t, expectedSnapshot2, entry.ExecutionSnapshot)
   600  }
   601  
   602  func TestDerivedDataTableCommitReadOnlyTransactionWithInvalidation(t *testing.T) {
   603  	block := newEmptyTestBlock()
   604  
   605  	testSetupTxn1Time := logical.Time(2)
   606  	testSetupTxn1, err := block.NewTableTransaction(0, testSetupTxn1Time)
   607  	require.NoError(t, err)
   608  
   609  	testSetupTxn2, err := block.NewTableTransaction(0, 4)
   610  	require.NoError(t, err)
   611  
   612  	testTxnTime := logical.Time(6)
   613  	testTxn, err := block.NewTableTransaction(0, testTxnTime)
   614  	require.NoError(t, err)
   615  
   616  	testSetupTxn1Invalidator := &testInvalidator{
   617  		invalidateName: "blah",
   618  	}
   619  	testSetupTxn1.AddInvalidator(testSetupTxn1Invalidator)
   620  
   621  	err = testSetupTxn1.Commit()
   622  	require.NoError(t, err)
   623  
   624  	key1 := "key1"
   625  	valStr1 := "v1"
   626  	expectedValue1 := &valStr1
   627  	expectedSnapshot1 := &snapshot.ExecutionSnapshot{}
   628  
   629  	testSetupTxn2.SetForTestingOnly(key1, expectedValue1, expectedSnapshot1)
   630  
   631  	key2 := "key2"
   632  	valStr2 := "v2"
   633  	expectedValue2 := &valStr2
   634  	expectedSnapshot2 := &snapshot.ExecutionSnapshot{}
   635  
   636  	testSetupTxn2.SetForTestingOnly(key2, expectedValue2, expectedSnapshot2)
   637  
   638  	err = testSetupTxn2.Commit()
   639  	require.NoError(t, err)
   640  
   641  	actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(key1)
   642  	require.True(t, ok)
   643  	require.Same(t, expectedValue1, actualValue)
   644  	require.Same(t, expectedSnapshot1, actualSnapshot)
   645  
   646  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(key2)
   647  	require.True(t, ok)
   648  	require.Same(t, expectedValue2, actualValue)
   649  	require.Same(t, expectedSnapshot2, actualSnapshot)
   650  
   651  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly("key3")
   652  	require.False(t, ok)
   653  	require.Nil(t, actualValue)
   654  	require.Nil(t, actualSnapshot)
   655  
   656  	testTxnInvalidator := &testInvalidator{invalidateAll: true}
   657  	testTxn.AddInvalidator(testTxnInvalidator)
   658  
   659  	err = testTxn.Commit()
   660  	require.NoError(t, err)
   661  
   662  	// Sanity check
   663  
   664  	require.Equal(
   665  		t,
   666  		testTxnTime,
   667  		block.LatestCommitExecutionTimeForTestingOnly())
   668  
   669  	require.Equal(
   670  		t,
   671  		chainedTableInvalidators[string, *string]{
   672  			{
   673  				TableInvalidator: testSetupTxn1Invalidator,
   674  				executionTime:    testSetupTxn1Time,
   675  			},
   676  			{
   677  				TableInvalidator: testTxnInvalidator,
   678  				executionTime:    testTxnTime,
   679  			},
   680  		},
   681  		block.InvalidatorsForTestingOnly())
   682  
   683  	require.Equal(t, 0, len(block.EntriesForTestingOnly()))
   684  }
   685  
   686  func TestDerivedDataTableCommitValidateError(t *testing.T) {
   687  	block := newEmptyTestBlock()
   688  
   689  	testSetupTxn, err := block.NewTableTransaction(0, 10)
   690  	require.NoError(t, err)
   691  
   692  	err = testSetupTxn.Commit()
   693  	require.NoError(t, err)
   694  
   695  	testTxn, err := block.NewTableTransaction(10, 10)
   696  	require.NoError(t, err)
   697  
   698  	commitErr := testTxn.Commit()
   699  	require.ErrorContains(t, commitErr, "non-increasing time")
   700  	require.False(t, errors.IsRetryableConflictError(commitErr))
   701  }
   702  
   703  func TestDerivedDataTableCommitRejectCommitGapForNormalTxn(t *testing.T) {
   704  	block := newEmptyTestBlock()
   705  
   706  	commitTime := logical.Time(5)
   707  	testSetupTxn, err := block.NewTableTransaction(0, commitTime)
   708  	require.NoError(t, err)
   709  
   710  	err = testSetupTxn.Commit()
   711  	require.NoError(t, err)
   712  
   713  	require.Equal(
   714  		t,
   715  		commitTime,
   716  		block.LatestCommitExecutionTimeForTestingOnly())
   717  
   718  	testTxn, err := block.NewTableTransaction(10, 10)
   719  	require.NoError(t, err)
   720  
   721  	err = testTxn.Validate()
   722  	require.NoError(t, err)
   723  
   724  	commitErr := testTxn.Commit()
   725  	require.ErrorContains(t, commitErr, "missing commit range [6, 10)")
   726  	require.False(t, errors.IsRetryableConflictError(commitErr))
   727  }
   728  
   729  func TestDerivedDataTableCommitSnapshotReadDontAdvanceTime(t *testing.T) {
   730  	block := newEmptyTestBlock()
   731  
   732  	commitTime := logical.Time(71)
   733  	testSetupTxn, err := block.NewTableTransaction(0, commitTime)
   734  	require.NoError(t, err)
   735  
   736  	err = testSetupTxn.Commit()
   737  	require.NoError(t, err)
   738  
   739  	for i := 0; i < 10; i++ {
   740  		txn := block.NewSnapshotReadTableTransaction()
   741  
   742  		err = txn.Commit()
   743  		require.NoError(t, err)
   744  	}
   745  
   746  	require.Equal(
   747  		t,
   748  		commitTime,
   749  		block.LatestCommitExecutionTimeForTestingOnly())
   750  }
   751  
   752  func TestDerivedDataTableCommitBadSnapshotReadInvalidator(t *testing.T) {
   753  	block := newEmptyTestBlock()
   754  
   755  	testTxn := block.NewSnapshotReadTableTransaction()
   756  
   757  	testTxn.AddInvalidator(&testInvalidator{invalidateAll: true})
   758  
   759  	commitErr := testTxn.Commit()
   760  	require.ErrorContains(t, commitErr, "snapshot read can't invalidate")
   761  	require.False(t, errors.IsRetryableConflictError(commitErr))
   762  }
   763  
   764  func TestDerivedDataTableCommitFineGrainInvalidation(t *testing.T) {
   765  	block := newEmptyTestBlock()
   766  
   767  	// Setup the database with two read entries
   768  
   769  	testSetupTxn, err := block.NewTableTransaction(0, 0)
   770  	require.NoError(t, err)
   771  
   772  	readKey1 := "read-key-1"
   773  	readValStr1 := "read-value-1"
   774  	readValue1 := &readValStr1
   775  	readSnapshot1 := &snapshot.ExecutionSnapshot{}
   776  
   777  	readKey2 := "read-key-2"
   778  	readValStr2 := "read-value-2"
   779  	readValue2 := &readValStr2
   780  	readSnapshot2 := &snapshot.ExecutionSnapshot{}
   781  
   782  	testSetupTxn.SetForTestingOnly(readKey1, readValue1, readSnapshot1)
   783  	testSetupTxn.SetForTestingOnly(readKey2, readValue2, readSnapshot2)
   784  
   785  	err = testSetupTxn.Commit()
   786  	require.NoError(t, err)
   787  
   788  	// Setup the test transaction by read both existing entries and writing
   789  	// two new ones,
   790  
   791  	testTxnTime := logical.Time(15)
   792  	testTxn, err := block.NewTableTransaction(1, testTxnTime)
   793  	require.NoError(t, err)
   794  
   795  	actualValue, actualSnapshot, ok := testTxn.GetForTestingOnly(readKey1)
   796  	require.True(t, ok)
   797  	require.Same(t, readValue1, actualValue)
   798  	require.Same(t, readSnapshot1, actualSnapshot)
   799  
   800  	actualValue, actualSnapshot, ok = testTxn.GetForTestingOnly(readKey2)
   801  	require.True(t, ok)
   802  	require.Same(t, readValue2, actualValue)
   803  	require.Same(t, readSnapshot2, actualSnapshot)
   804  
   805  	writeKey1 := "write key 1"
   806  	writeValStr1 := "write value 1"
   807  	writeValue1 := &writeValStr1
   808  	writeSnapshot1 := &snapshot.ExecutionSnapshot{}
   809  
   810  	writeKey2 := "write key 2"
   811  	writeValStr2 := "write value 2"
   812  	writeValue2 := &writeValStr2
   813  	writeSnapshot2 := &snapshot.ExecutionSnapshot{}
   814  
   815  	testTxn.SetForTestingOnly(writeKey1, writeValue1, writeSnapshot1)
   816  	testTxn.SetForTestingOnly(writeKey2, writeValue2, writeSnapshot2)
   817  
   818  	// Actual test.  Invalidate one pre-existing entry and one new entry.
   819  
   820  	invalidator1 := &testInvalidator{
   821  		invalidateName: readKey1,
   822  	}
   823  	invalidator2 := &testInvalidator{
   824  		invalidateName: writeKey1,
   825  	}
   826  	testTxn.AddInvalidator(nil)
   827  	testTxn.AddInvalidator(invalidator1)
   828  	testTxn.AddInvalidator(&testInvalidator{})
   829  	testTxn.AddInvalidator(invalidator2)
   830  	testTxn.AddInvalidator(&testInvalidator{})
   831  
   832  	err = testTxn.Commit()
   833  	require.NoError(t, err)
   834  
   835  	require.Equal(
   836  		t,
   837  		testTxnTime,
   838  		block.LatestCommitExecutionTimeForTestingOnly())
   839  
   840  	require.Equal(
   841  		t,
   842  		chainedTableInvalidators[string, *string]{
   843  			{
   844  				TableInvalidator: invalidator1,
   845  				executionTime:    testTxnTime,
   846  			},
   847  			{
   848  				TableInvalidator: invalidator2,
   849  				executionTime:    testTxnTime,
   850  			},
   851  		},
   852  		block.InvalidatorsForTestingOnly())
   853  
   854  	entries := block.EntriesForTestingOnly()
   855  	require.Equal(t, 2, len(entries))
   856  
   857  	entry, ok := entries[readKey2]
   858  	require.True(t, ok)
   859  	require.False(t, entry.isInvalid)
   860  	require.Same(t, readValue2, entry.Value)
   861  	require.Same(t, readSnapshot2, entry.ExecutionSnapshot)
   862  
   863  	entry, ok = entries[writeKey2]
   864  	require.True(t, ok)
   865  	require.False(t, entry.isInvalid)
   866  	require.Same(t, writeValue2, entry.Value)
   867  	require.Same(t, writeSnapshot2, entry.ExecutionSnapshot)
   868  }
   869  
   870  func TestDerivedDataTableNewChildDerivedBlockData(t *testing.T) {
   871  	parentBlock := newEmptyTestBlock()
   872  
   873  	require.Equal(
   874  		t,
   875  		logical.ParentBlockTime,
   876  		parentBlock.LatestCommitExecutionTimeForTestingOnly())
   877  	require.Equal(t, 0, len(parentBlock.InvalidatorsForTestingOnly()))
   878  	require.Equal(t, 0, len(parentBlock.EntriesForTestingOnly()))
   879  
   880  	txn, err := parentBlock.NewTableTransaction(0, 0)
   881  	require.NoError(t, err)
   882  
   883  	txn.AddInvalidator(&testInvalidator{invalidateAll: true})
   884  
   885  	err = txn.Commit()
   886  	require.NoError(t, err)
   887  
   888  	txn, err = parentBlock.NewTableTransaction(1, 1)
   889  	require.NoError(t, err)
   890  
   891  	key := "foo bar"
   892  	valStr := "zzz"
   893  	value := &valStr
   894  	state := &snapshot.ExecutionSnapshot{}
   895  
   896  	txn.SetForTestingOnly(key, value, state)
   897  
   898  	err = txn.Commit()
   899  	require.NoError(t, err)
   900  
   901  	// Sanity check parent block
   902  
   903  	require.Equal(
   904  		t,
   905  		logical.Time(1),
   906  		parentBlock.LatestCommitExecutionTimeForTestingOnly())
   907  
   908  	require.Equal(t, 1, len(parentBlock.InvalidatorsForTestingOnly()))
   909  
   910  	parentEntries := parentBlock.EntriesForTestingOnly()
   911  	require.Equal(t, 1, len(parentEntries))
   912  
   913  	parentEntry, ok := parentEntries[key]
   914  	require.True(t, ok)
   915  	require.False(t, parentEntry.isInvalid)
   916  	require.Same(t, value, parentEntry.Value)
   917  	require.Same(t, state, parentEntry.ExecutionSnapshot)
   918  
   919  	// Verify child is correctly initialized
   920  
   921  	childBlock := parentBlock.NewChildTable()
   922  
   923  	require.Equal(
   924  		t,
   925  		logical.ParentBlockTime,
   926  		childBlock.LatestCommitExecutionTimeForTestingOnly())
   927  
   928  	require.Equal(t, 0, len(childBlock.InvalidatorsForTestingOnly()))
   929  
   930  	childEntries := childBlock.EntriesForTestingOnly()
   931  	require.Equal(t, 1, len(childEntries))
   932  
   933  	childEntry, ok := childEntries[key]
   934  	require.True(t, ok)
   935  	require.False(t, childEntry.isInvalid)
   936  	require.Same(t, value, childEntry.Value)
   937  	require.Same(t, state, childEntry.ExecutionSnapshot)
   938  
   939  	require.NotSame(t, parentEntry, childEntry)
   940  }
   941  
   942  type testValueComputer struct {
   943  	valueFunc func() (int, error)
   944  	called    bool
   945  }
   946  
   947  func (computer *testValueComputer) Compute(
   948  	txnState state.NestedTransactionPreparer,
   949  	key flow.RegisterID,
   950  ) (
   951  	int,
   952  	error,
   953  ) {
   954  	computer.called = true
   955  	_, err := txnState.Get(key)
   956  	if err != nil {
   957  		return 0, err
   958  	}
   959  
   960  	return computer.valueFunc()
   961  }
   962  
   963  func TestDerivedDataTableGetOrCompute(t *testing.T) {
   964  	blockDerivedData := NewEmptyTable[flow.RegisterID, int](0)
   965  
   966  	key := flow.NewRegisterID(unittest.RandomAddressFixture(), "key")
   967  	value := 12345
   968  
   969  	t.Run("compute value", func(t *testing.T) {
   970  		txnState := state.NewTransactionState(
   971  			nil,
   972  			state.DefaultParameters())
   973  
   974  		txnDerivedData, err := blockDerivedData.NewTableTransaction(0, 0)
   975  		assert.NoError(t, err)
   976  
   977  		// first attempt to compute the value returns an error.
   978  		// But it's perfectly safe to handle the error and try again with the same txnState.
   979  		computer := &testValueComputer{
   980  			valueFunc: func() (int, error) { return 0, fmt.Errorf("compute error") },
   981  		}
   982  		_, err = txnDerivedData.GetOrCompute(txnState, key, computer)
   983  		assert.Error(t, err)
   984  		assert.Equal(t, 0, txnState.NumNestedTransactions())
   985  
   986  		// second attempt to compute the value succeeds.
   987  
   988  		computer = &testValueComputer{
   989  			valueFunc: func() (int, error) { return value, nil },
   990  		}
   991  		val, err := txnDerivedData.GetOrCompute(txnState, key, computer)
   992  		assert.NoError(t, err)
   993  		assert.Equal(t, value, val)
   994  		assert.True(t, computer.called)
   995  
   996  		snapshot, err := txnState.FinalizeMainTransaction()
   997  		assert.NoError(t, err)
   998  
   999  		_, found := snapshot.ReadSet[key]
  1000  		assert.True(t, found)
  1001  
  1002  		// Commit to setup the next test.
  1003  		err = txnDerivedData.Commit()
  1004  		assert.Nil(t, err)
  1005  	})
  1006  
  1007  	t.Run("get value", func(t *testing.T) {
  1008  		txnState := state.NewTransactionState(
  1009  			nil,
  1010  			state.DefaultParameters())
  1011  
  1012  		txnDerivedData, err := blockDerivedData.NewTableTransaction(1, 1)
  1013  		assert.NoError(t, err)
  1014  
  1015  		computer := &testValueComputer{
  1016  			valueFunc: func() (int, error) { return value, nil },
  1017  		}
  1018  		val, err := txnDerivedData.GetOrCompute(txnState, key, computer)
  1019  		assert.NoError(t, err)
  1020  		assert.Equal(t, value, val)
  1021  		assert.False(t, computer.called)
  1022  
  1023  		snapshot, err := txnState.FinalizeMainTransaction()
  1024  		assert.NoError(t, err)
  1025  
  1026  		_, found := snapshot.ReadSet[key]
  1027  		assert.True(t, found)
  1028  	})
  1029  }