github.com/koko1123/flow-go-1@v0.29.6/engine/consensus/matching/core_test.go (about)

     1  package matching
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/mock"
     8  	"github.com/stretchr/testify/suite"
     9  
    10  	"github.com/koko1123/flow-go-1/engine"
    11  	"github.com/koko1123/flow-go-1/model/flow"
    12  	"github.com/koko1123/flow-go-1/module/metrics"
    13  	mockmodule "github.com/koko1123/flow-go-1/module/mock"
    14  	"github.com/koko1123/flow-go-1/module/trace"
    15  	"github.com/koko1123/flow-go-1/storage"
    16  	"github.com/koko1123/flow-go-1/utils/unittest"
    17  )
    18  
    19  func TestMatchingCore(t *testing.T) {
    20  	suite.Run(t, new(MatchingSuite))
    21  }
    22  
    23  type MatchingSuite struct {
    24  	unittest.BaseChainSuite
    25  	// misc SERVICE COMPONENTS which are injected into Sealing Core
    26  	requester        *mockmodule.Requester
    27  	receiptValidator *mockmodule.ReceiptValidator
    28  
    29  	// MATCHING CORE
    30  	core *Core
    31  }
    32  
    33  func (ms *MatchingSuite) SetupTest() {
    34  	// ~~~~~~~~~~~~~~~~~~~~~~~~~~ SETUP SUITE ~~~~~~~~~~~~~~~~~~~~~~~~~~ //
    35  	ms.SetupChain()
    36  
    37  	metrics := metrics.NewNoopCollector()
    38  	tracer := trace.NewNoopTracer()
    39  
    40  	// ~~~~~~~~~~~~~~~~~~~~~~~ SETUP MATCHING CORE ~~~~~~~~~~~~~~~~~~~~~~~ //
    41  	ms.requester = new(mockmodule.Requester)
    42  	ms.receiptValidator = &mockmodule.ReceiptValidator{}
    43  
    44  	config := Config{
    45  		SealingThreshold:    10,
    46  		MaxResultsToRequest: 200,
    47  	}
    48  
    49  	ms.core = NewCore(
    50  		unittest.Logger(),
    51  		tracer,
    52  		metrics,
    53  		metrics,
    54  		ms.State,
    55  		ms.HeadersDB,
    56  		ms.ReceiptsDB,
    57  		ms.ReceiptsPL,
    58  		ms.PendingReceipts,
    59  		ms.SealsPL,
    60  		ms.receiptValidator,
    61  		ms.requester,
    62  		config,
    63  	)
    64  }
    65  
    66  // Test that we reject receipts for unknown blocks without generating an error
    67  func (ms *MatchingSuite) TestOnReceiptUnknownBlock() {
    68  	// This receipt has a random block ID, so the sealing Core won't find it.
    69  	receipt := unittest.ExecutionReceiptFixture()
    70  
    71  	// onReceipt should reject the receipt without throwing an error
    72  	_, err := ms.core.processReceipt(receipt)
    73  	ms.Require().NoError(err, "should drop receipt for unknown block without error")
    74  
    75  	ms.ReceiptsPL.AssertNumberOfCalls(ms.T(), "Add", 0)
    76  }
    77  
    78  // sealing Core should drop Result for known block that is already sealed
    79  // without trying to store anything
    80  func (ms *MatchingSuite) TestOnReceiptSealedResult() {
    81  	originID := ms.ExeID
    82  	receipt := unittest.ExecutionReceiptFixture(
    83  		unittest.WithExecutorID(originID),
    84  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.LatestSealedBlock))),
    85  	)
    86  
    87  	_, err := ms.core.processReceipt(receipt)
    88  	ms.Require().NoError(err, "should ignore receipt for sealed result")
    89  
    90  	ms.ReceiptsDB.AssertNumberOfCalls(ms.T(), "Store", 0)
    91  }
    92  
    93  // Test that we store different receipts for the same result
    94  func (ms *MatchingSuite) TestOnReceiptPendingResult() {
    95  	originID := ms.ExeID
    96  	receipt := unittest.ExecutionReceiptFixture(
    97  		unittest.WithExecutorID(originID),
    98  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.UnfinalizedBlock))),
    99  	)
   100  	ms.receiptValidator.On("Validate", receipt).Return(nil)
   101  
   102  	// Expect the receipt to be added to mempool and persistent storage
   103  	ms.ReceiptsPL.On("AddReceipt", receipt, ms.UnfinalizedBlock.Header).Return(true, nil).Once()
   104  	ms.ReceiptsDB.On("Store", receipt).Return(nil).Once()
   105  
   106  	_, err := ms.core.processReceipt(receipt)
   107  	ms.Require().NoError(err, "should handle different receipts for already pending result")
   108  	ms.ReceiptsPL.AssertExpectations(ms.T())
   109  	ms.ReceiptsDB.AssertExpectations(ms.T())
   110  }
   111  
   112  // TestOnReceipt_ReceiptInPersistentStorage verifies that Sealing Core adds
   113  // a receipt to the mempool, even if it is already in persistent storage. This
   114  // can happen after a crash, where the mempools got wiped
   115  func (ms *MatchingSuite) TestOnReceipt_ReceiptInPersistentStorage() {
   116  	originID := ms.ExeID
   117  	receipt := unittest.ExecutionReceiptFixture(
   118  		unittest.WithExecutorID(originID),
   119  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.UnfinalizedBlock))),
   120  	)
   121  	ms.receiptValidator.On("Validate", receipt).Return(nil)
   122  
   123  	// Persistent storage layer for Receipts has the receipt already stored
   124  	ms.ReceiptsDB.On("Store", receipt).Return(storage.ErrAlreadyExists).Once()
   125  	// The receipt should be added to the receipts mempool
   126  	ms.ReceiptsPL.On("AddReceipt", receipt, ms.UnfinalizedBlock.Header).Return(true, nil).Once()
   127  
   128  	_, err := ms.core.processReceipt(receipt)
   129  	ms.Require().NoError(err, "should process receipts, even if it is already in storage")
   130  	ms.ReceiptsPL.AssertExpectations(ms.T())
   131  	ms.ReceiptsDB.AssertNumberOfCalls(ms.T(), "Store", 1)
   132  }
   133  
   134  // try to submit a receipt that should be valid
   135  func (ms *MatchingSuite) TestOnReceiptValid() {
   136  	originID := ms.ExeID
   137  	receipt := unittest.ExecutionReceiptFixture(
   138  		unittest.WithExecutorID(originID),
   139  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.UnfinalizedBlock))),
   140  	)
   141  
   142  	ms.receiptValidator.On("Validate", receipt).Return(nil).Once()
   143  
   144  	// Expect the receipt to be added to mempool and persistent storage
   145  	ms.ReceiptsPL.On("AddReceipt", receipt, ms.UnfinalizedBlock.Header).Return(true, nil).Once()
   146  	ms.ReceiptsDB.On("Store", receipt).Return(nil).Once()
   147  
   148  	// onReceipt should run to completion without throwing an error
   149  	_, err := ms.core.processReceipt(receipt)
   150  	ms.Require().NoError(err, "should add receipt and result to mempools if valid")
   151  
   152  	ms.receiptValidator.AssertExpectations(ms.T())
   153  	ms.ReceiptsPL.AssertExpectations(ms.T())
   154  	ms.ReceiptsDB.AssertExpectations(ms.T())
   155  }
   156  
   157  // TestOnReceiptInvalid tests that we reject receipts that don't pass the ReceiptValidator
   158  func (ms *MatchingSuite) TestOnReceiptInvalid() {
   159  	// we use the same Receipt as in TestOnReceiptValid to ensure that the sealing Core is not
   160  	// rejecting the receipt for any other reason
   161  	originID := ms.ExeID
   162  	receipt := unittest.ExecutionReceiptFixture(
   163  		unittest.WithExecutorID(originID),
   164  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.UnfinalizedBlock))),
   165  	)
   166  
   167  	// check that _expected_ failure case of invalid receipt is handled without error
   168  	ms.receiptValidator.On("Validate", receipt).Return(engine.NewInvalidInputError("")).Once()
   169  	_, err := ms.core.processReceipt(receipt)
   170  	ms.Require().NoError(err, "invalid receipt should be dropped but not error")
   171  
   172  	// check that _unexpected_ failure case causes the error to be escalated
   173  	ms.receiptValidator.On("Validate", receipt).Return(fmt.Errorf("")).Once()
   174  	_, err = ms.core.processReceipt(receipt)
   175  	ms.Require().Error(err, "unexpected errors should be escalated")
   176  
   177  	ms.receiptValidator.AssertExpectations(ms.T())
   178  	ms.ReceiptsDB.AssertNumberOfCalls(ms.T(), "Store", 0)
   179  }
   180  
   181  // TestOnUnverifiableReceipt tests handling of receipts that are unverifiable
   182  // (e.g. if the parent result is unknown)
   183  func (ms *MatchingSuite) TestOnUnverifiableReceipt() {
   184  	// we use the same Receipt as in TestOnReceiptValid to ensure that the matching Core is not
   185  	// rejecting the receipt for any other reason
   186  	originID := ms.ExeID
   187  	receipt := unittest.ExecutionReceiptFixture(
   188  		unittest.WithExecutorID(originID),
   189  		unittest.WithResult(unittest.ExecutionResultFixture(unittest.WithBlock(&ms.UnfinalizedBlock))),
   190  	)
   191  
   192  	ms.PendingReceipts.On("Add", receipt).Return(false).Once()
   193  
   194  	// check that _expected_ failure case of invalid receipt is handled without error
   195  	ms.receiptValidator.On("Validate", receipt).Return(engine.NewUnverifiableInputError("missing parent result")).Once()
   196  	wasAdded, err := ms.core.processReceipt(receipt)
   197  	ms.Require().NoError(err, "unverifiable receipt should be cached but not error")
   198  	ms.Require().False(wasAdded, "unverifiable receipt should be cached but not added to the node's validated information")
   199  
   200  	ms.receiptValidator.AssertExpectations(ms.T())
   201  	ms.ReceiptsDB.AssertNumberOfCalls(ms.T(), "Store", 0)
   202  	ms.PendingReceipts.AssertExpectations(ms.T())
   203  }
   204  
   205  // TestRequestPendingReceipts tests sealing.Core.requestPendingReceipts():
   206  //   - generate n=100 consecutive blocks, where the first one is sealed and the last one is final
   207  func (ms *MatchingSuite) TestRequestPendingReceipts() {
   208  	// create blocks
   209  	n := 100
   210  	orderedBlocks := make([]flow.Block, 0, n)
   211  	parentBlock := ms.UnfinalizedBlock
   212  	for i := 0; i < n; i++ {
   213  		block := unittest.BlockWithParentFixture(parentBlock.Header)
   214  		ms.Extend(block)
   215  		orderedBlocks = append(orderedBlocks, *block)
   216  		parentBlock = *block
   217  	}
   218  
   219  	// progress latest sealed and latest finalized:
   220  	ms.LatestSealedBlock = orderedBlocks[0]
   221  	ms.LatestFinalizedBlock = &orderedBlocks[n-1]
   222  
   223  	// Expecting all blocks to be requested: from sealed height + 1 up to (incl.) latest finalized
   224  	for i := 1; i < n; i++ {
   225  		id := orderedBlocks[i].ID()
   226  		ms.requester.On("Query", id, mock.Anything).Return().Once()
   227  	}
   228  	ms.SealsPL.On("All").Return([]*flow.IncorporatedResultSeal{}).Maybe()
   229  
   230  	// we have no receipts
   231  	ms.ReceiptsDB.On("ByBlockID", mock.Anything).Return(nil, nil)
   232  
   233  	_, _, err := ms.core.requestPendingReceipts()
   234  	ms.Require().NoError(err, "should request results for pending blocks")
   235  	ms.requester.AssertExpectations(ms.T()) // asserts that requester.Query(<blockID>, filter.Any) was called
   236  }
   237  
   238  // TestRequestSecondPendingReceipt verifies that a second receipt is re-requested
   239  // Situation A:
   240  //   - we have _once_ receipt for an unsealed finalized block in storage
   241  //   - Expected: Method Core.requestPendingReceipts() should re-request a second receipt
   242  //
   243  // Situation B:
   244  //   - we have _two_ receipts for an unsealed finalized block storage
   245  //   - Expected: Method Core.requestPendingReceipts() should _not_ request another receipt
   246  //
   247  // TODO: this test is temporarily requires as long as sealing.Core requires _two_ receipts from different ENs to seal
   248  func (ms *MatchingSuite) TestRequestSecondPendingReceipt() {
   249  
   250  	ms.core.config.SealingThreshold = 0 // request receipts for all unsealed finalized blocks
   251  
   252  	result := unittest.ExecutionResultFixture(unittest.WithBlock(ms.LatestFinalizedBlock))
   253  
   254  	// make receipts:
   255  	receipt1 := unittest.ExecutionReceiptFixture(unittest.WithResult(result))
   256  	receipt2 := unittest.ExecutionReceiptFixture(unittest.WithResult(result))
   257  
   258  	// receipts from storage are potentially added to receipts mempool and incorporated results mempool
   259  	ms.ReceiptsPL.On("AddReceipt", receipt1, ms.LatestFinalizedBlock.Header).Return(false, nil).Maybe()
   260  	ms.ReceiptsPL.On("AddReceipt", receipt2, ms.LatestFinalizedBlock.Header).Return(false, nil).Maybe()
   261  
   262  	// Situation A: we have _once_ receipt for an unsealed finalized block in storage
   263  	ms.ReceiptsDB.On("ByBlockID", ms.LatestFinalizedBlock.ID()).Return(flow.ExecutionReceiptList{receipt1}, nil).Once()
   264  	ms.requester.On("Query", ms.LatestFinalizedBlock.ID(), mock.Anything).Return().Once() // Core should trigger requester to re-request a second receipt
   265  	_, _, err := ms.core.requestPendingReceipts()
   266  	ms.Require().NoError(err, "should request results for pending blocks")
   267  	ms.requester.AssertExpectations(ms.T()) // asserts that requester.Query(<blockID>, filter.Any) was called
   268  
   269  	// Situation B: we have _two_ receipts for an unsealed finalized block storage
   270  	ms.ReceiptsDB.On("ByBlockID", ms.LatestFinalizedBlock.ID()).Return(flow.ExecutionReceiptList{receipt1, receipt2}, nil).Once()
   271  	_, _, err = ms.core.requestPendingReceipts()
   272  	ms.Require().NoError(err, "should request results for pending blocks")
   273  	ms.requester.AssertExpectations(ms.T()) // asserts that requester.Query(<blockID>, filter.Any) was called
   274  }