github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunkVerifier_test.go (about)

     1  package chunks_test
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/onflow/cadence/runtime"
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  	"github.com/stretchr/testify/suite"
    14  
    15  	executionState "github.com/koko1123/flow-go-1/engine/execution/state"
    16  	"github.com/koko1123/flow-go-1/fvm"
    17  	fvmErrors "github.com/koko1123/flow-go-1/fvm/errors"
    18  	"github.com/koko1123/flow-go-1/fvm/state"
    19  	"github.com/koko1123/flow-go-1/ledger"
    20  	completeLedger "github.com/koko1123/flow-go-1/ledger/complete"
    21  	"github.com/koko1123/flow-go-1/ledger/complete/wal/fixtures"
    22  	chunksmodels "github.com/koko1123/flow-go-1/model/chunks"
    23  	"github.com/koko1123/flow-go-1/model/convert"
    24  	convertfixtures "github.com/koko1123/flow-go-1/model/convert/fixtures"
    25  	"github.com/koko1123/flow-go-1/model/flow"
    26  	"github.com/koko1123/flow-go-1/model/verification"
    27  	"github.com/koko1123/flow-go-1/module/chunks"
    28  	"github.com/koko1123/flow-go-1/module/metrics"
    29  	"github.com/koko1123/flow-go-1/utils/unittest"
    30  )
    31  
    32  var eventsList = flow.EventsList{
    33  	{
    34  		Type:             "event.someType",
    35  		TransactionID:    flow.Identifier{2, 3, 2, 3},
    36  		TransactionIndex: 1,
    37  		EventIndex:       2,
    38  		Payload:          []byte{7, 3, 1, 2},
    39  	},
    40  	{
    41  		Type:             "event.otherType",
    42  		TransactionID:    flow.Identifier{3, 3, 3},
    43  		TransactionIndex: 4,
    44  		EventIndex:       4,
    45  		Payload:          []byte{7, 3, 1, 2},
    46  	},
    47  }
    48  
    49  // the chain we use for this test suite
    50  var testChain = flow.Emulator
    51  var epochSetupEvent, _ = convertfixtures.EpochSetupFixtureByChainID(testChain)
    52  var epochCommitEvent, _ = convertfixtures.EpochCommitFixtureByChainID(testChain)
    53  
    54  var epochSetupServiceEvent, _ = convert.ServiceEvent(testChain, epochSetupEvent)
    55  
    56  var serviceEventsList = []flow.ServiceEvent{
    57  	*epochSetupServiceEvent,
    58  }
    59  
    60  type ChunkVerifierTestSuite struct {
    61  	suite.Suite
    62  	verifier          *chunks.ChunkVerifier
    63  	systemOkVerifier  *chunks.ChunkVerifier
    64  	systemBadVerifier *chunks.ChunkVerifier
    65  }
    66  
    67  // Make sure variables are set properly
    68  // SetupTest is executed prior to each individual test in this test suite
    69  func (s *ChunkVerifierTestSuite) SetupSuite() {
    70  	// seed the RNG
    71  	rand.Seed(time.Now().UnixNano())
    72  
    73  	vm := new(vmMock)
    74  	systemOkVm := new(vmSystemOkMock)
    75  	systemBadVm := new(vmSystemBadMock)
    76  	vmCtx := fvm.NewContext(fvm.WithChain(testChain.Chain()))
    77  
    78  	// system chunk runs predefined system transaction, hence we can't distinguish
    79  	// based on its content and we need separate VMs
    80  	s.verifier = chunks.NewChunkVerifier(vm, vmCtx, zerolog.Nop())
    81  	s.systemOkVerifier = chunks.NewChunkVerifier(systemOkVm, vmCtx, zerolog.Nop())
    82  	s.systemBadVerifier = chunks.NewChunkVerifier(systemBadVm, vmCtx, zerolog.Nop())
    83  }
    84  
    85  // TestChunkVerifier invokes all the tests in this test suite
    86  func TestChunkVerifier(t *testing.T) {
    87  	suite.Run(t, new(ChunkVerifierTestSuite))
    88  }
    89  
    90  // TestHappyPath tests verification of the baseline verifiable chunk
    91  func (s *ChunkVerifierTestSuite) TestHappyPath() {
    92  	vch := GetBaselineVerifiableChunk(s.T(), "", false)
    93  	assert.NotNil(s.T(), vch)
    94  	spockSecret, chFaults, err := s.verifier.Verify(vch)
    95  	assert.Nil(s.T(), err)
    96  	assert.Nil(s.T(), chFaults)
    97  	assert.NotNil(s.T(), spockSecret)
    98  }
    99  
   100  // TestMissingRegisterTouchForUpdate tests verification given a chunkdatapack missing a register touch (update)
   101  func (s *ChunkVerifierTestSuite) TestMissingRegisterTouchForUpdate() {
   102  	unittest.SkipUnless(s.T(), unittest.TEST_DEPRECATED, "Check new partial ledger for missing keys")
   103  
   104  	vch := GetBaselineVerifiableChunk(s.T(), "", false)
   105  	assert.NotNil(s.T(), vch)
   106  	// remove the second register touch
   107  	// vch.ChunkDataPack.RegisterTouches = vch.ChunkDataPack.RegisterTouches[:1]
   108  	spockSecret, chFaults, err := s.verifier.Verify(vch)
   109  	assert.Nil(s.T(), err)
   110  	assert.NotNil(s.T(), chFaults)
   111  	assert.Nil(s.T(), spockSecret)
   112  	_, ok := chFaults.(*chunksmodels.CFMissingRegisterTouch)
   113  	assert.True(s.T(), ok)
   114  }
   115  
   116  // TestMissingRegisterTouchForRead tests verification given a chunkdatapack missing a register touch (read)
   117  func (s *ChunkVerifierTestSuite) TestMissingRegisterTouchForRead() {
   118  	unittest.SkipUnless(s.T(), unittest.TEST_DEPRECATED, "Check new partial ledger for missing keys")
   119  
   120  	vch := GetBaselineVerifiableChunk(s.T(), "", false)
   121  	assert.NotNil(s.T(), vch)
   122  	// remove the second register touch
   123  	// vch.ChunkDataPack.RegisterTouches = vch.ChunkDataPack.RegisterTouches[1:]
   124  	spockSecret, chFaults, err := s.verifier.Verify(vch)
   125  	assert.Nil(s.T(), err)
   126  	assert.NotNil(s.T(), chFaults)
   127  	assert.Nil(s.T(), spockSecret)
   128  	_, ok := chFaults.(*chunksmodels.CFMissingRegisterTouch)
   129  	assert.True(s.T(), ok)
   130  }
   131  
   132  // TestWrongEndState tests verification covering the case
   133  // the state commitment computed after updating the partial trie
   134  // doesn't match the one provided by the chunks
   135  func (s *ChunkVerifierTestSuite) TestWrongEndState() {
   136  	vch := GetBaselineVerifiableChunk(s.T(), "wrongEndState", false)
   137  	assert.NotNil(s.T(), vch)
   138  	spockSecret, chFaults, err := s.verifier.Verify(vch)
   139  	assert.Nil(s.T(), err)
   140  	assert.NotNil(s.T(), chFaults)
   141  	assert.Nil(s.T(), spockSecret)
   142  	_, ok := chFaults.(*chunksmodels.CFNonMatchingFinalState)
   143  	assert.True(s.T(), ok)
   144  }
   145  
   146  // TestFailedTx tests verification behavior in case
   147  // of failed transaction. if a transaction fails, it should
   148  // still change the state commitment.
   149  func (s *ChunkVerifierTestSuite) TestFailedTx() {
   150  	vch := GetBaselineVerifiableChunk(s.T(), "failedTx", false)
   151  	assert.NotNil(s.T(), vch)
   152  	spockSecret, chFaults, err := s.verifier.Verify(vch)
   153  	assert.Nil(s.T(), err)
   154  	assert.Nil(s.T(), chFaults)
   155  	assert.NotNil(s.T(), spockSecret)
   156  }
   157  
   158  // TestEventsMismatch tests verification behavior in case
   159  // of emitted events not matching chunks
   160  func (s *ChunkVerifierTestSuite) TestEventsMismatch() {
   161  	vch := GetBaselineVerifiableChunk(s.T(), "eventsMismatch", false)
   162  	assert.NotNil(s.T(), vch)
   163  	_, chFault, err := s.verifier.Verify(vch)
   164  	assert.Nil(s.T(), err)
   165  	assert.NotNil(s.T(), chFault)
   166  	assert.IsType(s.T(), &chunksmodels.CFInvalidEventsCollection{}, chFault)
   167  }
   168  
   169  // TestServiceEventsMismatch tests verification behavior in case
   170  // of emitted service events not matching chunks'
   171  func (s *ChunkVerifierTestSuite) TestServiceEventsMismatch() {
   172  	vch := GetBaselineVerifiableChunk(s.T(), "doesn't matter", true)
   173  	assert.NotNil(s.T(), vch)
   174  	_, chFault, err := s.systemBadVerifier.Verify(vch)
   175  	assert.Nil(s.T(), err)
   176  	assert.NotNil(s.T(), chFault)
   177  	assert.IsType(s.T(), &chunksmodels.CFInvalidServiceEventsEmitted{}, chFault)
   178  }
   179  
   180  // TestServiceEventsAreChecked ensures that service events are in fact checked
   181  func (s *ChunkVerifierTestSuite) TestServiceEventsAreChecked() {
   182  	vch := GetBaselineVerifiableChunk(s.T(), "doesn't matter", true)
   183  	assert.NotNil(s.T(), vch)
   184  	_, chFault, err := s.systemOkVerifier.Verify(vch)
   185  	assert.Nil(s.T(), err)
   186  	assert.Nil(s.T(), chFault)
   187  }
   188  
   189  // TestEmptyCollection tests verification behaviour if a
   190  // collection doesn't have any transaction.
   191  func (s *ChunkVerifierTestSuite) TestEmptyCollection() {
   192  	vch := GetBaselineVerifiableChunk(s.T(), "", false)
   193  	assert.NotNil(s.T(), vch)
   194  	col := unittest.CollectionFixture(0)
   195  	vch.ChunkDataPack.Collection = &col
   196  	vch.EndState = vch.ChunkDataPack.StartState
   197  	emptyListHash, err := flow.EventsMerkleRootHash(flow.EventsList{})
   198  	assert.NoError(s.T(), err)
   199  	vch.Chunk.EventCollection = emptyListHash // empty collection emits no events
   200  	spockSecret, chFaults, err := s.verifier.Verify(vch)
   201  	assert.Nil(s.T(), err)
   202  	assert.Nil(s.T(), chFaults)
   203  	assert.NotNil(s.T(), spockSecret)
   204  }
   205  
   206  // GetBaselineVerifiableChunk returns a verifiable chunk and sets the script
   207  // of a transaction in the middle of the collection to some value to signal the
   208  // mocked vm on what to return as tx exec outcome.
   209  func GetBaselineVerifiableChunk(t *testing.T, script string, system bool) *verification.VerifiableChunkData {
   210  
   211  	// Collection setup
   212  
   213  	collectionSize := 5
   214  	magicTxIndex := 3
   215  	coll := unittest.CollectionFixture(collectionSize)
   216  	coll.Transactions[magicTxIndex] = &flow.TransactionBody{Script: []byte(script)}
   217  
   218  	guarantee := coll.Guarantee()
   219  
   220  	// Block setup
   221  	payload := flow.Payload{
   222  		Guarantees: []*flow.CollectionGuarantee{&guarantee},
   223  	}
   224  	header := unittest.BlockHeaderFixture()
   225  	header.PayloadHash = payload.Hash()
   226  	block := flow.Block{
   227  		Header:  header,
   228  		Payload: &payload,
   229  	}
   230  	blockID := block.ID()
   231  
   232  	// registerTouch and State setup
   233  	id1 := flow.NewRegisterID("00", "")
   234  	value1 := []byte{'a'}
   235  
   236  	id2Bytes := make([]byte, 32)
   237  	id2Bytes[0] = byte(5)
   238  	id2 := flow.NewRegisterID("05", "")
   239  	value2 := []byte{'b'}
   240  	UpdatedValue2 := []byte{'B'}
   241  
   242  	ids := make([]flow.RegisterID, 0)
   243  	values := make([]flow.RegisterValue, 0)
   244  	ids = append(ids, id1, id2)
   245  	values = append(values, value1, value2)
   246  
   247  	var verifiableChunkData verification.VerifiableChunkData
   248  
   249  	metricsCollector := &metrics.NoopCollector{}
   250  
   251  	f, _ := completeLedger.NewLedger(&fixtures.NoopWAL{}, 1000, metricsCollector, zerolog.Nop(), completeLedger.DefaultPathFinderVersion)
   252  
   253  	compactor := fixtures.NewNoopCompactor(f)
   254  	<-compactor.Ready()
   255  
   256  	defer func() {
   257  		<-f.Done()
   258  		<-compactor.Done()
   259  	}()
   260  
   261  	keys := executionState.RegisterIDSToKeys(ids)
   262  	update, err := ledger.NewUpdate(
   263  		f.InitialState(),
   264  		keys,
   265  		executionState.RegisterValuesToValues(values),
   266  	)
   267  
   268  	require.NoError(t, err)
   269  
   270  	startState, _, err := f.Set(update)
   271  	require.NoError(t, err)
   272  
   273  	query, err := ledger.NewQuery(startState, keys)
   274  	require.NoError(t, err)
   275  
   276  	proof, err := f.Prove(query)
   277  	require.NoError(t, err)
   278  
   279  	ids = []flow.RegisterID{id2}
   280  	values = [][]byte{UpdatedValue2}
   281  
   282  	keys = executionState.RegisterIDSToKeys(ids)
   283  	update, err = ledger.NewUpdate(
   284  		startState,
   285  		keys,
   286  		executionState.RegisterValuesToValues(values),
   287  	)
   288  	require.NoError(t, err)
   289  
   290  	endState, _, err := f.Set(update)
   291  	require.NoError(t, err)
   292  
   293  	// events
   294  	chunkEvents := make(flow.EventsList, 0)
   295  
   296  	erServiceEvents := make([]flow.ServiceEvent, 0)
   297  
   298  	if system {
   299  		chunkEvents = flow.EventsList{}
   300  		erServiceEvents = serviceEventsList
   301  	} else {
   302  		for i := 0; i < collectionSize; i++ {
   303  			if i == magicTxIndex {
   304  				switch script {
   305  				case "failedTx":
   306  					continue
   307  				}
   308  			}
   309  			chunkEvents = append(chunkEvents, eventsList...)
   310  		}
   311  	}
   312  
   313  	EventsMerkleRootHash, err := flow.EventsMerkleRootHash(chunkEvents)
   314  	require.NoError(t, err)
   315  
   316  	// Chunk setup
   317  	chunk := flow.Chunk{
   318  		ChunkBody: flow.ChunkBody{
   319  			CollectionIndex: 0,
   320  			StartState:      flow.StateCommitment(startState),
   321  			BlockID:         blockID,
   322  			EventCollection: EventsMerkleRootHash,
   323  		},
   324  		Index: 0,
   325  	}
   326  
   327  	chunkDataPack := flow.ChunkDataPack{
   328  		ChunkID:    chunk.ID(),
   329  		StartState: flow.StateCommitment(startState),
   330  		Proof:      proof,
   331  		Collection: &coll,
   332  	}
   333  
   334  	// ExecutionResult setup
   335  	result := flow.ExecutionResult{
   336  		BlockID:       blockID,
   337  		Chunks:        flow.ChunkList{&chunk},
   338  		ServiceEvents: erServiceEvents,
   339  	}
   340  
   341  	verifiableChunkData = verification.VerifiableChunkData{
   342  		IsSystemChunk: system,
   343  		Chunk:         &chunk,
   344  		Header:        header,
   345  		Result:        &result,
   346  		ChunkDataPack: &chunkDataPack,
   347  		EndState:      flow.StateCommitment(endState),
   348  	}
   349  
   350  	return &verifiableChunkData
   351  }
   352  
   353  type vmMock struct{}
   354  
   355  func (vm *vmMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error {
   356  	tx, ok := proc.(*fvm.TransactionProcedure)
   357  	if !ok {
   358  		return fmt.Errorf("invokable is not a transaction")
   359  	}
   360  
   361  	switch string(tx.Transaction.Script) {
   362  	case "wrongEndState":
   363  		// add updates to the ledger
   364  		_ = led.Set("00", "", []byte{'F'})
   365  		tx.Logs = []string{"log1", "log2"}
   366  		tx.Events = eventsList
   367  	case "failedTx":
   368  		// add updates to the ledger
   369  		_ = led.Set("05", "", []byte{'B'})
   370  		tx.Err = fvmErrors.NewCadenceRuntimeError(runtime.Error{}) // inside the runtime (e.g. div by zero, access account)
   371  	case "eventsMismatch":
   372  		tx.Events = append(eventsList, flow.Event{
   373  			Type:             "event.Extra",
   374  			TransactionID:    flow.Identifier{2, 3},
   375  			TransactionIndex: 0,
   376  			EventIndex:       0,
   377  			Payload:          []byte{88},
   378  		})
   379  	default:
   380  		_, _ = led.Get("00", "")
   381  		_, _ = led.Get("05", "")
   382  		_ = led.Set("05", "", []byte{'B'})
   383  		tx.Logs = []string{"log1", "log2"}
   384  		tx.Events = eventsList
   385  	}
   386  
   387  	return nil
   388  }
   389  
   390  func (vmMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) {
   391  	panic("not expected")
   392  }
   393  
   394  type vmSystemOkMock struct{}
   395  
   396  func (vm *vmSystemOkMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error {
   397  	tx, ok := proc.(*fvm.TransactionProcedure)
   398  	if !ok {
   399  		return fmt.Errorf("invokable is not a transaction")
   400  	}
   401  
   402  	tx.ServiceEvents = []flow.Event{epochSetupEvent}
   403  
   404  	// add "default" interaction expected in tests
   405  	_, _ = led.Get("00", "")
   406  	_, _ = led.Get("05", "")
   407  	_ = led.Set("05", "", []byte{'B'})
   408  	tx.Logs = []string{"log1", "log2"}
   409  
   410  	return nil
   411  }
   412  
   413  func (vmSystemOkMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) {
   414  	panic("not expected")
   415  }
   416  
   417  type vmSystemBadMock struct{}
   418  
   419  func (vm *vmSystemBadMock) Run(ctx fvm.Context, proc fvm.Procedure, led state.View) error {
   420  	tx, ok := proc.(*fvm.TransactionProcedure)
   421  	if !ok {
   422  		return fmt.Errorf("invokable is not a transaction")
   423  	}
   424  	// EpochSetup event is expected, but we emit EpochCommit here resulting in a chunk fault
   425  	tx.ServiceEvents = []flow.Event{epochCommitEvent}
   426  
   427  	return nil
   428  }
   429  
   430  func (vmSystemBadMock) GetAccount(_ fvm.Context, _ flow.Address, _ state.View) (*flow.Account, error) {
   431  	panic("not expected")
   432  }