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 }