github.com/onflow/flow-go@v0.33.17/engine/execution/computation/programs_test.go (about)

     1  package computation
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/ipfs/go-datastore"
    10  	dssync "github.com/ipfs/go-datastore/sync"
    11  	blockstore "github.com/ipfs/go-ipfs-blockstore"
    12  	"github.com/onflow/cadence"
    13  	"github.com/onflow/cadence/encoding/ccf"
    14  	"github.com/rs/zerolog"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/mock"
    17  	"github.com/stretchr/testify/require"
    18  
    19  	"github.com/onflow/flow-go/engine/execution"
    20  	"github.com/onflow/flow-go/engine/execution/computation/committer"
    21  	"github.com/onflow/flow-go/engine/execution/computation/computer"
    22  	"github.com/onflow/flow-go/engine/execution/testutil"
    23  	"github.com/onflow/flow-go/fvm"
    24  	"github.com/onflow/flow-go/fvm/storage/derived"
    25  	"github.com/onflow/flow-go/fvm/storage/snapshot"
    26  	"github.com/onflow/flow-go/model/flow"
    27  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    28  	"github.com/onflow/flow-go/module/executiondatasync/provider"
    29  	mocktracker "github.com/onflow/flow-go/module/executiondatasync/tracker/mock"
    30  	"github.com/onflow/flow-go/module/mempool/entity"
    31  	"github.com/onflow/flow-go/module/metrics"
    32  	module "github.com/onflow/flow-go/module/mock"
    33  	requesterunit "github.com/onflow/flow-go/module/state_synchronization/requester/unittest"
    34  	"github.com/onflow/flow-go/module/trace"
    35  	"github.com/onflow/flow-go/utils/unittest"
    36  )
    37  
    38  const (
    39  	testMaxConcurrency = 2
    40  )
    41  
    42  func TestPrograms_TestContractUpdates(t *testing.T) {
    43  	chain := flow.Mainnet.Chain()
    44  	vm := fvm.NewVirtualMachine()
    45  	execCtx := fvm.NewContext(fvm.WithChain(chain))
    46  
    47  	privateKeys, err := testutil.GenerateAccountPrivateKeys(1)
    48  	require.NoError(t, err)
    49  	snapshotTree, accounts, err := testutil.CreateAccounts(
    50  		vm,
    51  		testutil.RootBootstrappedLedger(vm, execCtx),
    52  		privateKeys,
    53  		chain)
    54  	require.NoError(t, err)
    55  
    56  	// setup transactions
    57  	account := accounts[0]
    58  	privKey := privateKeys[0]
    59  	// tx1 deploys contract version 1
    60  	tx1 := testutil.DeployEventContractTransaction(account, chain, 1)
    61  	prepareTx(t, tx1, account, privKey, 0, chain)
    62  
    63  	// tx2 calls the method of the contract (version 1)
    64  	tx2 := testutil.CreateEmitEventTransaction(account, account)
    65  	prepareTx(t, tx2, account, privKey, 1, chain)
    66  
    67  	// tx3 updates the contract to version 2
    68  	tx3 := testutil.UpdateEventContractTransaction(account, chain, 2)
    69  	prepareTx(t, tx3, account, privKey, 2, chain)
    70  
    71  	// tx4 calls the method of the contract (version 2)
    72  	tx4 := testutil.CreateEmitEventTransaction(account, account)
    73  	prepareTx(t, tx4, account, privKey, 3, chain)
    74  
    75  	// tx5 updates the contract to version 3 but fails (no env signature of service account)
    76  	tx5 := testutil.UnauthorizedDeployEventContractTransaction(account, chain, 3)
    77  	tx5.SetProposalKey(account, 0, 4).SetPayer(account)
    78  	err = testutil.SignEnvelope(tx5, account, privKey)
    79  	require.NoError(t, err)
    80  
    81  	// tx6 calls the method of the contract (version 2 expected)
    82  	tx6 := testutil.CreateEmitEventTransaction(account, account)
    83  	prepareTx(t, tx6, account, privKey, 5, chain)
    84  
    85  	transactions := []*flow.TransactionBody{tx1, tx2, tx3, tx4, tx5, tx6}
    86  
    87  	col := flow.Collection{Transactions: transactions}
    88  
    89  	guarantee := flow.CollectionGuarantee{
    90  		CollectionID: col.ID(),
    91  		Signature:    nil,
    92  	}
    93  
    94  	block := flow.Block{
    95  		Header: &flow.Header{
    96  			View: 26,
    97  		},
    98  		Payload: &flow.Payload{
    99  			Guarantees: []*flow.CollectionGuarantee{&guarantee},
   100  		},
   101  	}
   102  
   103  	executableBlock := &entity.ExecutableBlock{
   104  		Block: &block,
   105  		CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{
   106  			guarantee.ID(): {
   107  				Guarantee:    &guarantee,
   108  				Transactions: transactions,
   109  			},
   110  		},
   111  		StartState: unittest.StateCommitmentPointerFixture(),
   112  	}
   113  
   114  	me := new(module.Local)
   115  	me.On("NodeID").Return(unittest.IdentifierFixture())
   116  	me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil)
   117  	me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything).
   118  		Return(nil, nil)
   119  
   120  	bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   121  	trackerStorage := mocktracker.NewMockStorage()
   122  
   123  	prov := provider.NewProvider(
   124  		zerolog.Nop(),
   125  		metrics.NewNoopCollector(),
   126  		execution_data.DefaultSerializer,
   127  		bservice,
   128  		trackerStorage,
   129  	)
   130  
   131  	blockComputer, err := computer.NewBlockComputer(
   132  		vm,
   133  		execCtx,
   134  		metrics.NewNoopCollector(),
   135  		trace.NewNoopTracer(),
   136  		zerolog.Nop(),
   137  		committer.NewNoopViewCommitter(),
   138  		me,
   139  		prov,
   140  		nil,
   141  		testutil.ProtocolStateWithSourceFixture(nil),
   142  		testMaxConcurrency)
   143  	require.NoError(t, err)
   144  
   145  	derivedChainData, err := derived.NewDerivedChainData(10)
   146  	require.NoError(t, err)
   147  
   148  	engine := &Manager{
   149  		blockComputer:    blockComputer,
   150  		derivedChainData: derivedChainData,
   151  	}
   152  
   153  	returnedComputationResult, err := engine.ComputeBlock(
   154  		context.Background(),
   155  		unittest.IdentifierFixture(),
   156  		executableBlock,
   157  		snapshotTree)
   158  	require.NoError(t, err)
   159  
   160  	events := returnedComputationResult.AllEvents()
   161  
   162  	// first event should be contract deployed
   163  	assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type)
   164  
   165  	// second event should have a value of 1 (since is calling version 1 of contract)
   166  	hasValidEventValue(t, events[1], 1)
   167  
   168  	// third event should be contract updated
   169  	assert.EqualValues(t, "flow.AccountContractUpdated", events[2].Type)
   170  
   171  	// 4th event should have a value of 2 (since is calling version 2 of contract)
   172  	hasValidEventValue(t, events[3], 2)
   173  
   174  	// 5th event should have a value of 2 (since is calling version 2 of contract)
   175  	hasValidEventValue(t, events[4], 2)
   176  }
   177  
   178  type blockProvider struct {
   179  	blocks map[uint64]*flow.Block
   180  }
   181  
   182  func (b blockProvider) ByHeightFrom(height uint64, _ *flow.Header) (*flow.Header, error) {
   183  	block, has := b.blocks[height]
   184  	if has {
   185  		return block.Header, nil
   186  	}
   187  	return nil, fmt.Errorf("block for height (%d) is not available", height)
   188  }
   189  
   190  // TestPrograms_TestBlockForks tests the functionality of
   191  // derivedChainData under contract deployment and contract updates on
   192  // different block forks
   193  //
   194  // block structure and operations
   195  // Block1 (empty block)
   196  //
   197  //	    -> Block11 (deploy contract v1)
   198  //	        -> Block111  (emit event - version should be 1) and (update contract to v3)
   199  //	            -> Block1111   (emit event - version should be 3)
   200  //		       -> Block112 (emit event - version should be 1) and (update contract to v4)
   201  //	            -> Block1121  (emit event - version should be 4)
   202  //	    -> Block12 (deploy contract v2)
   203  //	        -> Block121 (emit event - version should be 2)
   204  //	            -> Block1211 (emit event - version should be 2)
   205  func TestPrograms_TestBlockForks(t *testing.T) {
   206  	block := unittest.BlockFixture()
   207  	chain := flow.Emulator.Chain()
   208  	vm := fvm.NewVirtualMachine()
   209  	execCtx := fvm.NewContext(
   210  		fvm.WithBlockHeader(block.Header),
   211  		fvm.WithBlocks(blockProvider{map[uint64]*flow.Block{0: &block}}),
   212  		fvm.WithChain(chain))
   213  
   214  	privateKeys, err := testutil.GenerateAccountPrivateKeys(1)
   215  	require.NoError(t, err)
   216  	snapshotTree, accounts, err := testutil.CreateAccounts(
   217  		vm,
   218  		testutil.RootBootstrappedLedger(vm, execCtx),
   219  		privateKeys,
   220  		chain)
   221  	require.NoError(t, err)
   222  
   223  	account := accounts[0]
   224  	privKey := privateKeys[0]
   225  
   226  	me := new(module.Local)
   227  	me.On("NodeID").Return(unittest.IdentifierFixture())
   228  	me.On("Sign", mock.Anything, mock.Anything).Return(nil, nil)
   229  	me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything).
   230  		Return(nil, nil)
   231  
   232  	bservice := requesterunit.MockBlobService(blockstore.NewBlockstore(dssync.MutexWrap(datastore.NewMapDatastore())))
   233  	trackerStorage := mocktracker.NewMockStorage()
   234  
   235  	prov := provider.NewProvider(
   236  		zerolog.Nop(),
   237  		metrics.NewNoopCollector(),
   238  		execution_data.DefaultSerializer,
   239  		bservice,
   240  		trackerStorage,
   241  	)
   242  
   243  	blockComputer, err := computer.NewBlockComputer(
   244  		vm,
   245  		execCtx,
   246  		metrics.NewNoopCollector(),
   247  		trace.NewNoopTracer(),
   248  		zerolog.Nop(),
   249  		committer.NewNoopViewCommitter(),
   250  		me,
   251  		prov,
   252  		nil,
   253  		testutil.ProtocolStateWithSourceFixture(nil),
   254  		testMaxConcurrency)
   255  	require.NoError(t, err)
   256  
   257  	derivedChainData, err := derived.NewDerivedChainData(10)
   258  	require.NoError(t, err)
   259  
   260  	engine := &Manager{
   261  		blockComputer:    blockComputer,
   262  		derivedChainData: derivedChainData,
   263  	}
   264  
   265  	var (
   266  		res *execution.ComputationResult
   267  
   268  		block1, block11, block111, block112, block1121,
   269  		block1111, block12, block121, block1211 *flow.Block
   270  
   271  		block1Snapshot, block11Snapshot, block111Snapshot, block112Snapshot,
   272  		block12Snapshot, block121Snapshot snapshot.SnapshotTree
   273  	)
   274  
   275  	t.Run("executing block1 (no collection)", func(t *testing.T) {
   276  		block1 = &flow.Block{
   277  			Header: &flow.Header{
   278  				View: 1,
   279  			},
   280  			Payload: &flow.Payload{
   281  				Guarantees: []*flow.CollectionGuarantee{},
   282  			},
   283  		}
   284  		block1Snapshot = snapshotTree
   285  		executableBlock := &entity.ExecutableBlock{
   286  			Block:      block1,
   287  			StartState: unittest.StateCommitmentPointerFixture(),
   288  		}
   289  		_, err := engine.ComputeBlock(
   290  			context.Background(),
   291  			unittest.IdentifierFixture(),
   292  			executableBlock,
   293  			block1Snapshot)
   294  		require.NoError(t, err)
   295  	})
   296  
   297  	t.Run("executing block11 (deploys contract version 1)", func(t *testing.T) {
   298  		block11tx1 := testutil.DeployEventContractTransaction(account, chain, 1)
   299  		prepareTx(t, block11tx1, account, privKey, 0, chain)
   300  
   301  		txs11 := []*flow.TransactionBody{block11tx1}
   302  		col11 := flow.Collection{Transactions: txs11}
   303  		block11, res, block11Snapshot = createTestBlockAndRun(
   304  			t,
   305  			engine,
   306  			block1,
   307  			col11,
   308  			block1Snapshot)
   309  		// cache should include value for this block
   310  		require.NotNil(t, derivedChainData.Get(block11.ID()))
   311  		// 1st event should be contract deployed
   312  
   313  		assert.EqualValues(t, "flow.AccountContractAdded", res.AllEvents()[0].Type)
   314  	})
   315  
   316  	t.Run("executing block111 (emit event (expected v1), update contract to v3)", func(t *testing.T) {
   317  		block111ExpectedValue := 1
   318  		// emit event
   319  		block111tx1 := testutil.CreateEmitEventTransaction(account, account)
   320  		prepareTx(t, block111tx1, account, privKey, 1, chain)
   321  
   322  		// update contract version 3
   323  		block111tx2 := testutil.UpdateEventContractTransaction(account, chain, 3)
   324  		prepareTx(t, block111tx2, account, privKey, 2, chain)
   325  
   326  		col111 := flow.Collection{Transactions: []*flow.TransactionBody{block111tx1, block111tx2}}
   327  		block111, res, block111Snapshot = createTestBlockAndRun(
   328  			t,
   329  			engine,
   330  			block11,
   331  			col111,
   332  			block11Snapshot)
   333  		// cache should include a program for this block
   334  		require.NotNil(t, derivedChainData.Get(block111.ID()))
   335  
   336  		events := res.AllEvents()
   337  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   338  
   339  		// 1st event
   340  		hasValidEventValue(t, events[0], block111ExpectedValue)
   341  		// second event should be contract deployed
   342  		assert.EqualValues(t, "flow.AccountContractUpdated", events[1].Type)
   343  	})
   344  
   345  	t.Run("executing block1111 (emit event (expected v3))", func(t *testing.T) {
   346  		block1111ExpectedValue := 3
   347  		block1111tx1 := testutil.CreateEmitEventTransaction(account, account)
   348  		prepareTx(t, block1111tx1, account, privKey, 3, chain)
   349  
   350  		col1111 := flow.Collection{Transactions: []*flow.TransactionBody{block1111tx1}}
   351  		block1111, res, _ = createTestBlockAndRun(
   352  			t,
   353  			engine,
   354  			block111,
   355  			col1111,
   356  			block111Snapshot)
   357  		// cache should include a program for this block
   358  		require.NotNil(t, derivedChainData.Get(block1111.ID()))
   359  
   360  		events := res.AllEvents()
   361  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   362  
   363  		// 1st event
   364  		hasValidEventValue(t, events[0], block1111ExpectedValue)
   365  	})
   366  
   367  	t.Run("executing block112 (emit event (expected v1))", func(t *testing.T) {
   368  		block112ExpectedValue := 1
   369  		block112tx1 := testutil.CreateEmitEventTransaction(account, account)
   370  		prepareTx(t, block112tx1, account, privKey, 1, chain)
   371  
   372  		// update contract version 4
   373  		block112tx2 := testutil.UpdateEventContractTransaction(account, chain, 4)
   374  		prepareTx(t, block112tx2, account, privKey, 2, chain)
   375  
   376  		col112 := flow.Collection{Transactions: []*flow.TransactionBody{block112tx1, block112tx2}}
   377  		block112, res, block112Snapshot = createTestBlockAndRun(
   378  			t,
   379  			engine,
   380  			block11,
   381  			col112,
   382  			block11Snapshot)
   383  		// cache should include a program for this block
   384  		require.NotNil(t, derivedChainData.Get(block112.ID()))
   385  
   386  		events := res.AllEvents()
   387  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   388  
   389  		// 1st event
   390  		hasValidEventValue(t, events[0], block112ExpectedValue)
   391  		// second event should be contract deployed
   392  		assert.EqualValues(t, "flow.AccountContractUpdated", events[1].Type)
   393  
   394  	})
   395  	t.Run("executing block1121 (emit event (expected v4))", func(t *testing.T) {
   396  		block1121ExpectedValue := 4
   397  		block1121tx1 := testutil.CreateEmitEventTransaction(account, account)
   398  		prepareTx(t, block1121tx1, account, privKey, 3, chain)
   399  
   400  		col1121 := flow.Collection{Transactions: []*flow.TransactionBody{block1121tx1}}
   401  		block1121, res, _ = createTestBlockAndRun(
   402  			t,
   403  			engine,
   404  			block112,
   405  			col1121,
   406  			block112Snapshot)
   407  		// cache should include a program for this block
   408  		require.NotNil(t, derivedChainData.Get(block1121.ID()))
   409  
   410  		events := res.AllEvents()
   411  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   412  
   413  		// 1st event
   414  		hasValidEventValue(t, events[0], block1121ExpectedValue)
   415  
   416  	})
   417  	t.Run("executing block12 (deploys contract V2)", func(t *testing.T) {
   418  
   419  		block12tx1 := testutil.DeployEventContractTransaction(account, chain, 2)
   420  		prepareTx(t, block12tx1, account, privKey, 0, chain)
   421  
   422  		col12 := flow.Collection{Transactions: []*flow.TransactionBody{block12tx1}}
   423  		block12, res, block12Snapshot = createTestBlockAndRun(
   424  			t,
   425  			engine,
   426  			block1,
   427  			col12,
   428  			block1Snapshot)
   429  		// cache should include a program for this block
   430  		require.NotNil(t, derivedChainData.Get(block12.ID()))
   431  
   432  		events := res.AllEvents()
   433  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   434  
   435  		assert.EqualValues(t, "flow.AccountContractAdded", events[0].Type)
   436  	})
   437  	t.Run("executing block121 (emit event (expected V2)", func(t *testing.T) {
   438  		block121ExpectedValue := 2
   439  		block121tx1 := testutil.CreateEmitEventTransaction(account, account)
   440  		prepareTx(t, block121tx1, account, privKey, 1, chain)
   441  
   442  		col121 := flow.Collection{Transactions: []*flow.TransactionBody{block121tx1}}
   443  		block121, res, block121Snapshot = createTestBlockAndRun(
   444  			t,
   445  			engine,
   446  			block12,
   447  			col121,
   448  			block12Snapshot)
   449  		// cache should include a program for this block
   450  		require.NotNil(t, derivedChainData.Get(block121.ID()))
   451  
   452  		events := res.AllEvents()
   453  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   454  
   455  		// 1st event
   456  		hasValidEventValue(t, events[0], block121ExpectedValue)
   457  	})
   458  	t.Run("executing Block1211 (emit event (expected V2)", func(t *testing.T) {
   459  		block1211ExpectedValue := 2
   460  		block1211tx1 := testutil.CreateEmitEventTransaction(account, account)
   461  		prepareTx(t, block1211tx1, account, privKey, 2, chain)
   462  
   463  		col1211 := flow.Collection{Transactions: []*flow.TransactionBody{block1211tx1}}
   464  		block1211, res, _ = createTestBlockAndRun(
   465  			t,
   466  			engine,
   467  			block121,
   468  			col1211,
   469  			block121Snapshot)
   470  		// cache should include a program for this block
   471  		require.NotNil(t, derivedChainData.Get(block1211.ID()))
   472  		// had no change so cache should be equal to parent
   473  		require.Equal(t, derivedChainData.Get(block121.ID()), derivedChainData.Get(block1211.ID()))
   474  
   475  		events := res.AllEvents()
   476  		require.Equal(t, res.BlockExecutionResult.Size(), 2)
   477  
   478  		// 1st event
   479  		hasValidEventValue(t, events[0], block1211ExpectedValue)
   480  	})
   481  
   482  }
   483  
   484  func createTestBlockAndRun(
   485  	t *testing.T,
   486  	engine *Manager,
   487  	parentBlock *flow.Block,
   488  	col flow.Collection,
   489  	snapshotTree snapshot.SnapshotTree,
   490  ) (
   491  	*flow.Block,
   492  	*execution.ComputationResult,
   493  	snapshot.SnapshotTree,
   494  ) {
   495  	guarantee := flow.CollectionGuarantee{
   496  		CollectionID: col.ID(),
   497  		Signature:    nil,
   498  	}
   499  
   500  	block := &flow.Block{
   501  		Header: &flow.Header{
   502  			ParentID:  parentBlock.ID(),
   503  			View:      parentBlock.Header.Height + 1,
   504  			Timestamp: time.Now(),
   505  		},
   506  		Payload: &flow.Payload{
   507  			Guarantees: []*flow.CollectionGuarantee{&guarantee},
   508  		},
   509  	}
   510  
   511  	executableBlock := &entity.ExecutableBlock{
   512  		Block: block,
   513  		CompleteCollections: map[flow.Identifier]*entity.CompleteCollection{
   514  			guarantee.ID(): {
   515  				Guarantee:    &guarantee,
   516  				Transactions: col.Transactions,
   517  			},
   518  		},
   519  		StartState: unittest.StateCommitmentPointerFixture(),
   520  	}
   521  	returnedComputationResult, err := engine.ComputeBlock(
   522  		context.Background(),
   523  		unittest.IdentifierFixture(),
   524  		executableBlock,
   525  		snapshotTree)
   526  	require.NoError(t, err)
   527  
   528  	for _, txResult := range returnedComputationResult.AllTransactionResults() {
   529  		require.Empty(t, txResult.ErrorMessage)
   530  	}
   531  
   532  	for _, snapshot := range returnedComputationResult.AllExecutionSnapshots() {
   533  		snapshotTree = snapshotTree.Append(snapshot)
   534  	}
   535  
   536  	return block, returnedComputationResult, snapshotTree
   537  }
   538  
   539  func prepareTx(t *testing.T,
   540  	tx *flow.TransactionBody,
   541  	account flow.Address,
   542  	privKey flow.AccountPrivateKey,
   543  	seqNumber uint64,
   544  	chain flow.Chain) {
   545  	tx.SetProposalKey(account, 0, seqNumber).
   546  		SetPayer(chain.ServiceAddress())
   547  	err := testutil.SignPayload(tx, account, privKey)
   548  	require.NoError(t, err)
   549  	err = testutil.SignEnvelope(tx, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey)
   550  	require.NoError(t, err)
   551  }
   552  
   553  func hasValidEventValue(t *testing.T, event flow.Event, value int) {
   554  	data, err := ccf.Decode(nil, event.Payload)
   555  	require.NoError(t, err)
   556  	assert.Equal(t, int16(value), data.(cadence.Event).Fields[0].ToGoValue())
   557  }