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

     1  package state_test
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/dgraph-io/badger/v2"
     9  	"github.com/rs/zerolog"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	"github.com/onflow/flow-go/engine/execution/state"
    13  	"github.com/onflow/flow-go/engine/execution/storehouse"
    14  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    15  	led "github.com/onflow/flow-go/ledger"
    16  	ledger "github.com/onflow/flow-go/ledger/complete"
    17  	"github.com/onflow/flow-go/ledger/complete/wal/fixtures"
    18  	"github.com/onflow/flow-go/model/flow"
    19  	"github.com/onflow/flow-go/module/metrics"
    20  	"github.com/onflow/flow-go/module/trace"
    21  	storageerr "github.com/onflow/flow-go/storage"
    22  	storage "github.com/onflow/flow-go/storage/mock"
    23  	"github.com/onflow/flow-go/utils/unittest"
    24  )
    25  
    26  func prepareTest(f func(t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, commits *storage.Commits)) func(*testing.T) {
    27  	return func(t *testing.T) {
    28  		unittest.RunWithBadgerDB(t, func(badgerDB *badger.DB) {
    29  			metricsCollector := &metrics.NoopCollector{}
    30  			diskWal := &fixtures.NoopWAL{}
    31  			ls, err := ledger.NewLedger(diskWal, 100, metricsCollector, zerolog.Nop(), ledger.DefaultPathFinderVersion)
    32  			require.NoError(t, err)
    33  			compactor := fixtures.NewNoopCompactor(ls)
    34  			<-compactor.Ready()
    35  			defer func() {
    36  				<-ls.Done()
    37  				<-compactor.Done()
    38  			}()
    39  
    40  			stateCommitments := storage.NewCommits(t)
    41  			headers := storage.NewHeaders(t)
    42  			blocks := storage.NewBlocks(t)
    43  			collections := storage.NewCollections(t)
    44  			events := storage.NewEvents(t)
    45  			serviceEvents := storage.NewServiceEvents(t)
    46  			txResults := storage.NewTransactionResults(t)
    47  			chunkDataPacks := storage.NewChunkDataPacks(t)
    48  			results := storage.NewExecutionResults(t)
    49  			myReceipts := storage.NewMyExecutionReceipts(t)
    50  
    51  			es := state.NewExecutionState(
    52  				ls, stateCommitments, blocks, headers, collections, chunkDataPacks, results, myReceipts, events, serviceEvents, txResults, badgerDB, trace.NewNoopTracer(),
    53  				nil,
    54  				false,
    55  			)
    56  
    57  			f(t, es, ls, headers, stateCommitments)
    58  		})
    59  	}
    60  }
    61  
    62  func TestExecutionStateWithTrieStorage(t *testing.T) {
    63  	t.Run("commit write and read new state", prepareTest(func(
    64  		t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, stateCommitments *storage.Commits) {
    65  		header1 := unittest.BlockHeaderFixture()
    66  		sc1 := flow.StateCommitment(l.InitialState())
    67  
    68  		reg1 := unittest.MakeOwnerReg("fruit", "apple")
    69  		reg2 := unittest.MakeOwnerReg("vegetable", "carrot")
    70  		executionSnapshot := &snapshot.ExecutionSnapshot{
    71  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
    72  				reg1.Key: reg1.Value,
    73  				reg2.Key: reg2.Value,
    74  			},
    75  		}
    76  
    77  		sc2, update, sc2Snapshot, err := state.CommitDelta(l, executionSnapshot,
    78  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1))
    79  		require.NoError(t, err)
    80  
    81  		// validate new snapshot
    82  		val, err := sc2Snapshot.Get(reg1.Key)
    83  		require.NoError(t, err)
    84  		require.Equal(t, reg1.Value, val)
    85  
    86  		val, err = sc2Snapshot.Get(reg2.Key)
    87  		require.NoError(t, err)
    88  		require.Equal(t, reg2.Value, val)
    89  
    90  		require.Equal(t, sc1[:], update.RootHash[:])
    91  		require.Len(t, update.Paths, 2)
    92  		require.Len(t, update.Payloads, 2)
    93  
    94  		// validate sc2
    95  		require.Equal(t, sc2, sc2Snapshot.Commitment())
    96  		validateUpdate(t, update, sc1, executionSnapshot)
    97  
    98  		header2 := unittest.BlockHeaderWithParentFixture(header1)
    99  		storageSnapshot := es.NewStorageSnapshot(sc2, header2.ID(), header2.Height)
   100  
   101  		b1, err := storageSnapshot.Get(reg1.Key)
   102  		require.NoError(t, err)
   103  		b2, err := storageSnapshot.Get(reg2.Key)
   104  		require.NoError(t, err)
   105  
   106  		require.Equal(t, flow.RegisterValue("apple"), b1)
   107  		require.Equal(t, flow.RegisterValue("carrot"), b2)
   108  
   109  		// verify has state
   110  		require.True(t, l.HasState(led.State(sc2)))
   111  		require.False(t, l.HasState(led.State(unittest.StateCommitmentFixture())))
   112  	}))
   113  
   114  	t.Run("commit write and read previous state", prepareTest(func(
   115  		t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, stateCommitments *storage.Commits) {
   116  		header1 := unittest.BlockHeaderFixture()
   117  		sc1 := flow.StateCommitment(l.InitialState())
   118  
   119  		reg1 := unittest.MakeOwnerReg("fruit", "apple")
   120  		executionSnapshot1 := &snapshot.ExecutionSnapshot{
   121  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   122  				reg1.Key: reg1.Value,
   123  			},
   124  		}
   125  
   126  		sc2, _, sc2Snapshot, err := state.CommitDelta(l, executionSnapshot1,
   127  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1),
   128  		)
   129  		require.NoError(t, err)
   130  
   131  		// update value and get resulting state commitment
   132  		executionSnapshot2 := &snapshot.ExecutionSnapshot{
   133  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   134  				reg1.Key: flow.RegisterValue("orange"),
   135  			},
   136  		}
   137  
   138  		sc3, _, _, err := state.CommitDelta(l, executionSnapshot2, sc2Snapshot)
   139  		require.NoError(t, err)
   140  
   141  		header2 := unittest.BlockHeaderWithParentFixture(header1)
   142  		// create a view for previous state version
   143  		storageSnapshot3 := es.NewStorageSnapshot(sc2, header2.ID(), header2.Height)
   144  
   145  		header3 := unittest.BlockHeaderWithParentFixture(header1)
   146  		// create a view for new state version
   147  		storageSnapshot4 := es.NewStorageSnapshot(sc3, header3.ID(), header3.Height)
   148  
   149  		// header2 and header3 are different blocks
   150  		require.True(t, header2.ID() != (header3.ID()))
   151  
   152  		// fetch the value at both versions
   153  		b1, err := storageSnapshot3.Get(reg1.Key)
   154  		require.NoError(t, err)
   155  
   156  		b2, err := storageSnapshot4.Get(reg1.Key)
   157  		require.NoError(t, err)
   158  
   159  		require.Equal(t, flow.RegisterValue("apple"), b1)
   160  		require.Equal(t, flow.RegisterValue("orange"), b2)
   161  	}))
   162  
   163  	t.Run("commit delta and read new state", prepareTest(func(
   164  		t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, stateCommitments *storage.Commits) {
   165  		header1 := unittest.BlockHeaderFixture()
   166  		sc1 := flow.StateCommitment(l.InitialState())
   167  
   168  		reg1 := unittest.MakeOwnerReg("fruit", "apple")
   169  		reg2 := unittest.MakeOwnerReg("vegetable", "carrot")
   170  		// set initial value
   171  		executionSnapshot1 := &snapshot.ExecutionSnapshot{
   172  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   173  				reg1.Key: reg1.Value,
   174  				reg2.Key: reg2.Value,
   175  			},
   176  		}
   177  
   178  		sc2, _, sc2Snapshot, err := state.CommitDelta(l, executionSnapshot1,
   179  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1),
   180  		)
   181  		require.NoError(t, err)
   182  
   183  		// update value and get resulting state commitment
   184  		executionSnapshot2 := &snapshot.ExecutionSnapshot{
   185  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   186  				reg1.Key: nil,
   187  			},
   188  		}
   189  
   190  		sc3, _, _, err := state.CommitDelta(l, executionSnapshot2, sc2Snapshot)
   191  		require.NoError(t, err)
   192  
   193  		header2 := unittest.BlockHeaderWithParentFixture(header1)
   194  		// create a view for previous state version
   195  		storageSnapshot3 := es.NewStorageSnapshot(sc2, header2.ID(), header2.Height)
   196  
   197  		header3 := unittest.BlockHeaderWithParentFixture(header2)
   198  		// create a view for new state version
   199  		storageSnapshot4 := es.NewStorageSnapshot(sc3, header3.ID(), header3.Height)
   200  
   201  		// fetch the value at both versions
   202  		b1, err := storageSnapshot3.Get(reg1.Key)
   203  		require.NoError(t, err)
   204  
   205  		b2, err := storageSnapshot4.Get(reg1.Key)
   206  		require.NoError(t, err)
   207  
   208  		require.Equal(t, flow.RegisterValue("apple"), b1)
   209  		require.Empty(t, b2)
   210  	}))
   211  
   212  	t.Run("commit delta and persist state commit for the second time should be OK", prepareTest(func(
   213  		t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, stateCommitments *storage.Commits) {
   214  		sc1 := flow.StateCommitment(l.InitialState())
   215  
   216  		reg1 := unittest.MakeOwnerReg("fruit", "apple")
   217  		reg2 := unittest.MakeOwnerReg("vegetable", "carrot")
   218  		// set initial value
   219  		executionSnapshot1 := &snapshot.ExecutionSnapshot{
   220  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   221  				reg1.Key: reg1.Value,
   222  				reg2.Key: reg2.Value,
   223  			},
   224  		}
   225  
   226  		sc2, _, _, err := state.CommitDelta(l, executionSnapshot1,
   227  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1),
   228  		)
   229  		require.NoError(t, err)
   230  
   231  		// committing for the second time should be OK
   232  		sc2Same, _, _, err := state.CommitDelta(l, executionSnapshot1,
   233  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1),
   234  		)
   235  		require.NoError(t, err)
   236  
   237  		require.Equal(t, sc2, sc2Same)
   238  	}))
   239  
   240  	t.Run("commit write and create snapshot", prepareTest(func(
   241  		t *testing.T, es state.ExecutionState, l *ledger.Ledger, headers *storage.Headers, stateCommitments *storage.Commits) {
   242  		header1 := unittest.BlockHeaderFixture()
   243  		header2 := unittest.BlockHeaderWithParentFixture(header1)
   244  		sc1 := flow.StateCommitment(l.InitialState())
   245  
   246  		reg1 := unittest.MakeOwnerReg("fruit", "apple")
   247  		reg2 := unittest.MakeOwnerReg("vegetable", "carrot")
   248  		executionSnapshot := &snapshot.ExecutionSnapshot{
   249  			WriteSet: map[flow.RegisterID]flow.RegisterValue{
   250  				reg1.Key: reg1.Value,
   251  				reg2.Key: reg2.Value,
   252  			},
   253  		}
   254  
   255  		sc2, _, _, err := state.CommitDelta(l, executionSnapshot,
   256  			storehouse.NewExecutingBlockSnapshot(state.NewLedgerStorageSnapshot(l, sc1), sc1))
   257  		require.NoError(t, err)
   258  
   259  		// test CreateStorageSnapshot for known and executed block
   260  		headers.On("ByBlockID", header2.ID()).Return(header2, nil)
   261  		stateCommitments.On("ByBlockID", header2.ID()).Return(sc2, nil)
   262  		snapshot2, h2, err := es.CreateStorageSnapshot(header2.ID())
   263  		require.NoError(t, err)
   264  		require.Equal(t, header2.ID(), h2.ID())
   265  
   266  		val, err := snapshot2.Get(reg1.Key)
   267  		require.NoError(t, err)
   268  		require.Equal(t, val, reg1.Value)
   269  
   270  		val, err = snapshot2.Get(reg2.Key)
   271  		require.NoError(t, err)
   272  		require.Equal(t, val, reg2.Value)
   273  
   274  		// test CreateStorageSnapshot for unknown block
   275  		unknown := unittest.BlockHeaderFixture()
   276  		headers.On("ByBlockID", unknown.ID()).Return(nil, fmt.Errorf("unknown: %w", storageerr.ErrNotFound))
   277  		_, _, err = es.CreateStorageSnapshot(unknown.ID())
   278  		require.Error(t, err)
   279  		require.True(t, errors.Is(err, storageerr.ErrNotFound))
   280  
   281  		// test CreateStorageSnapshot for known and unexecuted block
   282  		unexecuted := unittest.BlockHeaderFixture()
   283  		headers.On("ByBlockID", unexecuted.ID()).Return(unexecuted, nil)
   284  		stateCommitments.On("ByBlockID", unexecuted.ID()).Return(nil, fmt.Errorf("not found: %w", storageerr.ErrNotFound))
   285  		_, _, err = es.CreateStorageSnapshot(unexecuted.ID())
   286  		require.Error(t, err)
   287  		require.True(t, errors.Is(err, state.ErrNotExecuted))
   288  
   289  		// test CreateStorageSnapshot for pruned block
   290  		pruned := unittest.BlockHeaderFixture()
   291  		prunedState := unittest.StateCommitmentFixture()
   292  		headers.On("ByBlockID", pruned.ID()).Return(pruned, nil)
   293  		stateCommitments.On("ByBlockID", pruned.ID()).Return(prunedState, nil)
   294  		_, _, err = es.CreateStorageSnapshot(pruned.ID())
   295  		require.Error(t, err)
   296  		require.True(t, errors.Is(err, state.ErrExecutionStatePruned))
   297  	}))
   298  
   299  }