github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/verification/verifier/engine_test.go (about)

     1  package verifier_test
     2  
     3  import (
     4  	"crypto/rand"
     5  	"errors"
     6  	"testing"
     7  
     8  	"github.com/ipfs/go-cid"
     9  	testifymock "github.com/stretchr/testify/mock"
    10  	"github.com/stretchr/testify/require"
    11  	"github.com/stretchr/testify/suite"
    12  
    13  	"github.com/onflow/crypto"
    14  	"github.com/onflow/crypto/hash"
    15  
    16  	"github.com/onflow/flow-go/engine/testutil/mocklocal"
    17  	"github.com/onflow/flow-go/engine/verification/utils"
    18  	"github.com/onflow/flow-go/engine/verification/verifier"
    19  	chmodel "github.com/onflow/flow-go/model/chunks"
    20  	"github.com/onflow/flow-go/model/flow"
    21  	"github.com/onflow/flow-go/model/verification"
    22  	realModule "github.com/onflow/flow-go/module"
    23  	mockmodule "github.com/onflow/flow-go/module/mock"
    24  	"github.com/onflow/flow-go/module/trace"
    25  	"github.com/onflow/flow-go/network/channels"
    26  	"github.com/onflow/flow-go/network/mocknetwork"
    27  	protocol "github.com/onflow/flow-go/state/protocol/mock"
    28  	mockstorage "github.com/onflow/flow-go/storage/mock"
    29  	"github.com/onflow/flow-go/utils/unittest"
    30  )
    31  
    32  type VerifierEngineTestSuite struct {
    33  	suite.Suite
    34  	net           *mocknetwork.Network
    35  	tracer        realModule.Tracer
    36  	state         *protocol.State
    37  	ss            *protocol.Snapshot
    38  	me            *mocklocal.MockLocal
    39  	sk            crypto.PrivateKey
    40  	hasher        hash.Hasher
    41  	chain         flow.Chain
    42  	pushCon       *mocknetwork.Conduit // mocks con for submitting result approvals
    43  	pullCon       *mocknetwork.Conduit
    44  	metrics       *mockmodule.VerificationMetrics // mocks performance monitoring metrics
    45  	approvals     *mockstorage.ResultApprovals
    46  	chunkVerifier *mockmodule.ChunkVerifier
    47  }
    48  
    49  func TestVerifierEngine(t *testing.T) {
    50  	suite.Run(t, new(VerifierEngineTestSuite))
    51  }
    52  
    53  func (suite *VerifierEngineTestSuite) SetupTest() {
    54  	suite.state = new(protocol.State)
    55  	suite.net = mocknetwork.NewNetwork(suite.T())
    56  	suite.tracer = trace.NewNoopTracer()
    57  	suite.ss = new(protocol.Snapshot)
    58  	suite.pushCon = mocknetwork.NewConduit(suite.T())
    59  	suite.pullCon = mocknetwork.NewConduit(suite.T())
    60  	suite.metrics = mockmodule.NewVerificationMetrics(suite.T())
    61  	suite.chain = flow.Testnet.Chain()
    62  	suite.approvals = mockstorage.NewResultApprovals(suite.T())
    63  	suite.chunkVerifier = mockmodule.NewChunkVerifier(suite.T())
    64  
    65  	suite.net.On("Register", channels.PushApprovals, testifymock.Anything).
    66  		Return(suite.pushCon, nil).
    67  		Once()
    68  
    69  	suite.net.On("Register", channels.ProvideApprovalsByChunk, testifymock.Anything).
    70  		Return(suite.pullCon, nil).
    71  		Once()
    72  
    73  	suite.state.On("Final").Return(suite.ss)
    74  
    75  	// Mocks the signature oracle of the engine
    76  	//
    77  	// generates signing and verification keys
    78  	seed := make([]byte, crypto.KeyGenSeedMinLen)
    79  	n, err := rand.Read(seed)
    80  	require.Equal(suite.T(), n, crypto.KeyGenSeedMinLen)
    81  	require.NoError(suite.T(), err)
    82  
    83  	// creates private key of verification node
    84  	sk, err := crypto.GeneratePrivateKey(crypto.BLSBLS12381, seed)
    85  	require.NoError(suite.T(), err)
    86  	suite.sk = sk
    87  
    88  	// tag of hasher should be the same as the tag of engine's hasher
    89  	suite.hasher = utils.NewResultApprovalHasher()
    90  
    91  	// defines the identity of verification node and attaches its key.
    92  	verIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleVerification))
    93  	verIdentity.StakingPubKey = sk.PublicKey()
    94  	suite.me = mocklocal.NewMockLocal(sk, verIdentity.NodeID, suite.T())
    95  }
    96  
    97  func (suite *VerifierEngineTestSuite) getTestNewEngine() *verifier.Engine {
    98  	e, err := verifier.New(
    99  		unittest.Logger(),
   100  		suite.metrics,
   101  		suite.tracer,
   102  		suite.net,
   103  		suite.state,
   104  		suite.me,
   105  		suite.chunkVerifier,
   106  		suite.approvals)
   107  	require.Nil(suite.T(), err)
   108  
   109  	suite.net.AssertExpectations(suite.T())
   110  	return e
   111  
   112  }
   113  
   114  // TestVerifyHappyPath tests the verification path for a single verifiable chunk, which is
   115  // assigned to the verifier node, and is passed by the ingest engine
   116  // The tests evaluates that a result approval is emitted to all consensus nodes
   117  // about the input execution receipt
   118  func (suite *VerifierEngineTestSuite) TestVerifyHappyPath() {
   119  	eng := suite.getTestNewEngine()
   120  
   121  	consensusNodes := unittest.IdentityListFixture(1, unittest.WithRole(flow.RoleConsensus))
   122  	suite.ss.On("Identities", testifymock.Anything).Return(consensusNodes, nil)
   123  
   124  	vChunk := unittest.VerifiableChunkDataFixture(uint64(0))
   125  
   126  	tests := []struct {
   127  		name string
   128  		err  error
   129  	}{
   130  		// tests that a valid verifiable chunk is verified and a result approval is emitted
   131  		{
   132  			name: "chunk verified successfully",
   133  			err:  nil,
   134  		},
   135  		// tests that a verifiable chunk that triggers a CFMissingRegisterTouch fault still emits a result approval
   136  		{
   137  			name: "chunk failed with missing register touch fault",
   138  			err: chmodel.NewCFMissingRegisterTouch(
   139  				[]string{"test missing register touch"},
   140  				vChunk.Chunk.Index,
   141  				vChunk.Result.ID(),
   142  				unittest.TransactionFixture().ID()),
   143  		},
   144  	}
   145  
   146  	for _, test := range tests {
   147  		suite.Run(test.name, func() {
   148  			var expectedApproval *flow.ResultApproval
   149  
   150  			suite.approvals.
   151  				On("Store", testifymock.Anything).
   152  				Return(nil).
   153  				Run(func(args testifymock.Arguments) {
   154  					ra, ok := args[0].(*flow.ResultApproval)
   155  					suite.Require().True(ok)
   156  
   157  					suite.Assert().Equal(vChunk.Chunk.BlockID, ra.Body.BlockID)
   158  					suite.Assert().Equal(vChunk.Result.ID(), ra.Body.ExecutionResultID)
   159  					suite.Assert().Equal(vChunk.Chunk.Index, ra.Body.ChunkIndex)
   160  					suite.Assert().Equal(suite.me.NodeID(), ra.Body.ApproverID)
   161  
   162  					// verifies the signatures
   163  					atstID := ra.Body.Attestation.ID()
   164  					suite.Assert().True(suite.sk.PublicKey().Verify(ra.Body.AttestationSignature, atstID[:], suite.hasher))
   165  					bodyID := ra.Body.ID()
   166  					suite.Assert().True(suite.sk.PublicKey().Verify(ra.VerifierSignature, bodyID[:], suite.hasher))
   167  
   168  					// spock should be non-nil
   169  					suite.Assert().NotNil(ra.Body.Spock)
   170  
   171  					expectedApproval = ra
   172  				}).
   173  				Once()
   174  			suite.approvals.
   175  				On("Index", testifymock.Anything, testifymock.Anything, testifymock.Anything).
   176  				Return(nil).
   177  				Run(func(args testifymock.Arguments) {
   178  					erID, ok := args[0].(flow.Identifier)
   179  					suite.Require().True(ok)
   180  					suite.Assert().Equal(expectedApproval.Body.ExecutionResultID, erID)
   181  
   182  					chIndex, ok := args[1].(uint64)
   183  					suite.Require().True(ok)
   184  					suite.Assert().Equal(expectedApproval.Body.ChunkIndex, chIndex)
   185  
   186  					raID, ok := args[2].(flow.Identifier)
   187  					suite.Require().True(ok)
   188  					suite.Assert().Equal(expectedApproval.ID(), raID)
   189  				}).
   190  				Once()
   191  
   192  			suite.pushCon.
   193  				On("Publish", testifymock.Anything, testifymock.Anything).
   194  				Return(nil).
   195  				Run(func(args testifymock.Arguments) {
   196  					// check that the approval matches the input execution result
   197  					ra, ok := args[0].(*flow.ResultApproval)
   198  					suite.Require().True(ok)
   199  					suite.Assert().Equal(expectedApproval, ra)
   200  
   201  					// note: mock includes each variadic argument as a separate element in slice
   202  					node, ok := args[1].(flow.Identifier)
   203  					suite.Require().True(ok)
   204  					suite.Assert().Equal(consensusNodes.NodeIDs()[0], node)
   205  					suite.Assert().Len(args, 2) // only a single node should be in the list
   206  				}).
   207  				Once()
   208  
   209  			suite.metrics.On("OnVerifiableChunkReceivedAtVerifierEngine").Return().Once()
   210  			suite.metrics.On("OnResultApprovalDispatchedInNetworkByVerifier").Return().Once()
   211  
   212  			suite.chunkVerifier.On("Verify", vChunk).Return(nil, test.err).Once()
   213  
   214  			err := eng.ProcessLocal(vChunk)
   215  			suite.Assert().NoError(err)
   216  		})
   217  	}
   218  }
   219  
   220  func (suite *VerifierEngineTestSuite) TestVerifyUnhappyPaths() {
   221  	eng := suite.getTestNewEngine()
   222  
   223  	var tests = []struct {
   224  		errFn func(vc *verification.VerifiableChunkData) error
   225  	}{
   226  		// Note: skipping CFMissingRegisterTouch because it does emit a result approval
   227  		{
   228  			errFn: func(vc *verification.VerifiableChunkData) error {
   229  				return chmodel.NewCFInvalidVerifiableChunk(
   230  					"test",
   231  					errors.New("test invalid verifiable chunk"),
   232  					vc.Chunk.Index,
   233  					vc.Result.ID())
   234  			},
   235  		},
   236  		{
   237  			errFn: func(vc *verification.VerifiableChunkData) error {
   238  				return chmodel.NewCFNonMatchingFinalState(
   239  					unittest.StateCommitmentFixture(),
   240  					unittest.StateCommitmentFixture(),
   241  					vc.Chunk.Index,
   242  					vc.Result.ID())
   243  			},
   244  		},
   245  		{
   246  			errFn: func(vc *verification.VerifiableChunkData) error {
   247  				return chmodel.NewCFInvalidEventsCollection(
   248  					unittest.IdentifierFixture(),
   249  					unittest.IdentifierFixture(),
   250  					vc.Chunk.Index,
   251  					vc.Result.ID(),
   252  					flow.EventsList{})
   253  			},
   254  		},
   255  		{
   256  			errFn: func(vc *verification.VerifiableChunkData) error {
   257  				return chmodel.NewCFSystemChunkIncludedCollection(vc.Chunk.Index, vc.Result.ID())
   258  			},
   259  		},
   260  		{
   261  			errFn: func(vc *verification.VerifiableChunkData) error {
   262  				return chmodel.NewCFExecutionDataBlockIDMismatch(
   263  					unittest.IdentifierFixture(),
   264  					unittest.IdentifierFixture(),
   265  					vc.Chunk.Index,
   266  					vc.Result.ID())
   267  			},
   268  		},
   269  		{
   270  			errFn: func(vc *verification.VerifiableChunkData) error {
   271  				return chmodel.NewCFExecutionDataChunksLengthMismatch(
   272  					0,
   273  					0,
   274  					vc.Chunk.Index,
   275  					vc.Result.ID())
   276  			},
   277  		},
   278  		{
   279  			errFn: func(vc *verification.VerifiableChunkData) error {
   280  				return chmodel.NewCFExecutionDataInvalidChunkCID(
   281  					cid.Cid{},
   282  					cid.Cid{},
   283  					vc.Chunk.Index,
   284  					vc.Result.ID())
   285  			},
   286  		},
   287  		{
   288  			errFn: func(vc *verification.VerifiableChunkData) error {
   289  				return chmodel.NewCFInvalidExecutionDataID(
   290  					unittest.IdentifierFixture(),
   291  					unittest.IdentifierFixture(),
   292  					vc.Chunk.Index,
   293  					vc.Result.ID())
   294  			},
   295  		},
   296  		{
   297  			errFn: func(vc *verification.VerifiableChunkData) error {
   298  				return errors.New("test error")
   299  			},
   300  		},
   301  	}
   302  
   303  	for i, test := range tests {
   304  		vc := unittest.VerifiableChunkDataFixture(uint64(i))
   305  		expectedErr := test.errFn(vc)
   306  
   307  		suite.chunkVerifier.On("Verify", vc).Return(nil, expectedErr).Once()
   308  
   309  		suite.metrics.On("OnVerifiableChunkReceivedAtVerifierEngine").Return().Once()
   310  		// note: we shouldn't publish any result approval or emit OnResultApprovalDispatchedInNetworkByVerifier
   311  
   312  		err := eng.ProcessLocal(vc)
   313  
   314  		// no error returned from the engine
   315  		suite.Assert().NoError(err)
   316  	}
   317  }