github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/utils/unittest/chain_suite.go (about) 1 package unittest 2 3 import ( 4 "fmt" 5 6 "github.com/stretchr/testify/mock" 7 "github.com/stretchr/testify/require" 8 "github.com/stretchr/testify/suite" 9 10 "github.com/onflow/flow-go/model/chunks" 11 "github.com/onflow/flow-go/model/flow" 12 mempool "github.com/onflow/flow-go/module/mempool/mock" 13 module "github.com/onflow/flow-go/module/mock" 14 "github.com/onflow/flow-go/state" 15 realproto "github.com/onflow/flow-go/state/protocol" 16 protocol "github.com/onflow/flow-go/state/protocol/mock" 17 storerr "github.com/onflow/flow-go/storage" 18 storage "github.com/onflow/flow-go/storage/mock" 19 ) 20 21 type BaseChainSuite struct { 22 suite.Suite 23 24 // IDENTITIES 25 ConID flow.Identifier 26 ExeID flow.Identifier 27 VerID flow.Identifier 28 29 Identities map[flow.Identifier]*flow.Identity 30 Approvers flow.IdentityList 31 32 // BLOCKS 33 RootBlock flow.Block 34 LatestSealedBlock flow.Block 35 LatestFinalizedBlock *flow.Block 36 UnfinalizedBlock flow.Block 37 LatestExecutionResult *flow.ExecutionResult 38 Blocks map[flow.Identifier]*flow.Block 39 40 // PROTOCOL STATE 41 State *protocol.State 42 SealedSnapshot *protocol.Snapshot 43 FinalSnapshot *protocol.Snapshot 44 45 // MEMPOOLS and STORAGE which are injected into Matching Engine 46 // mock storage.ExecutionReceipts: backed by in-memory map PersistedReceipts 47 ReceiptsDB *storage.ExecutionReceipts 48 49 ResultsDB *storage.ExecutionResults 50 PersistedResults map[flow.Identifier]*flow.ExecutionResult 51 52 // mock mempool.IncorporatedResultSeals: backed by in-memory map PendingSeals 53 SealsPL *mempool.IncorporatedResultSeals 54 PendingSeals map[flow.Identifier]*flow.IncorporatedResultSeal 55 56 // mock BLOCK STORAGE: backed by in-memory map Blocks 57 HeadersDB *storage.Headers // backed by map Blocks 58 IndexDB *storage.Index // backed by map Blocks 59 PayloadsDB *storage.Payloads // backed by map Blocks 60 SealsDB *storage.Seals // backed by map SealsIndex 61 SealsIndex map[flow.Identifier]*flow.Seal // last valid seal for block 62 63 // mock mempool.ReceiptsForest: used to test whether or not Matching Engine stores receipts 64 ReceiptsPL *mempool.ExecutionTree 65 66 Assigner *module.ChunkAssigner 67 Assignments map[flow.Identifier]*chunks.Assignment // index for assignments for given execution result 68 69 PendingReceipts *mempool.PendingReceipts 70 } 71 72 func (bc *BaseChainSuite) SetupChain() { 73 74 // ~~~~~~~~~~~~~~~~~~~~~~~~~~ SETUP IDENTITIES ~~~~~~~~~~~~~~~~~~~~~~~~~~ // 75 76 // asign node Identities 77 con := IdentityFixture(WithRole(flow.RoleConsensus)) 78 exe := IdentityFixture(WithRole(flow.RoleExecution)) 79 ver := IdentityFixture(WithRole(flow.RoleVerification)) 80 81 bc.ConID = con.NodeID 82 bc.ExeID = exe.NodeID 83 bc.VerID = ver.NodeID 84 85 bc.Identities = make(map[flow.Identifier]*flow.Identity) 86 bc.Identities[bc.ConID] = con 87 bc.Identities[bc.ExeID] = exe 88 bc.Identities[bc.VerID] = ver 89 90 // assign 4 nodes to the verification role 91 bc.Approvers = IdentityListFixture(4, WithRole(flow.RoleVerification)) 92 for _, verifier := range bc.Approvers { 93 bc.Identities[verifier.ID()] = verifier 94 } 95 96 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SETUP BLOCKS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 97 // RootBlock <- LatestSealedBlock <- LatestFinalizedBlock <- UnfinalizedBlock 98 bc.RootBlock = BlockFixture() 99 bc.LatestSealedBlock = *BlockWithParentFixture(bc.RootBlock.Header) 100 latestFinalizedBlock := BlockWithParentFixture(bc.LatestSealedBlock.Header) 101 bc.LatestFinalizedBlock = latestFinalizedBlock 102 bc.UnfinalizedBlock = *BlockWithParentFixture(bc.LatestFinalizedBlock.Header) 103 104 bc.Blocks = make(map[flow.Identifier]*flow.Block) 105 bc.Blocks[bc.RootBlock.ID()] = &bc.RootBlock 106 bc.Blocks[bc.LatestSealedBlock.ID()] = &bc.LatestSealedBlock 107 bc.Blocks[bc.LatestFinalizedBlock.ID()] = bc.LatestFinalizedBlock 108 bc.Blocks[bc.UnfinalizedBlock.ID()] = &bc.UnfinalizedBlock 109 110 // ~~~~~~~~~~~~~~~~~~~~~~~~ SETUP PROTOCOL STATE ~~~~~~~~~~~~~~~~~~~~~~~~ // 111 bc.State = &protocol.State{} 112 113 // define the protocol state snapshot of the latest finalized block 114 bc.State.On("Final").Return( 115 func() realproto.Snapshot { 116 return bc.FinalSnapshot 117 }, 118 nil, 119 ) 120 bc.FinalSnapshot = &protocol.Snapshot{} 121 bc.FinalSnapshot.On("Head").Return( 122 func() *flow.Header { 123 return bc.LatestFinalizedBlock.Header 124 }, 125 nil, 126 ) 127 bc.FinalSnapshot.On("SealedResult").Return( 128 func() *flow.ExecutionResult { 129 blockID := bc.LatestFinalizedBlock.ID() 130 seal, found := bc.SealsIndex[blockID] 131 if !found { 132 return nil 133 } 134 result, found := bc.PersistedResults[seal.ResultID] 135 if !found { 136 return nil 137 } 138 return result 139 }, 140 func() *flow.Seal { 141 blockID := bc.LatestFinalizedBlock.ID() 142 seal, found := bc.SealsIndex[blockID] 143 if !found { 144 return nil 145 } 146 return seal 147 }, 148 func() error { 149 blockID := bc.LatestFinalizedBlock.ID() 150 seal, found := bc.SealsIndex[blockID] 151 if !found { 152 return storerr.ErrNotFound 153 } 154 _, found = bc.PersistedResults[seal.ResultID] 155 if !found { 156 return storerr.ErrNotFound 157 } 158 return nil 159 }, 160 ) 161 162 // define the protocol state snapshot of the latest sealed block 163 bc.State.On("Sealed").Return( 164 func() realproto.Snapshot { 165 return bc.SealedSnapshot 166 }, 167 nil, 168 ) 169 bc.SealedSnapshot = &protocol.Snapshot{} 170 bc.SealedSnapshot.On("Head").Return( 171 func() *flow.Header { 172 return bc.LatestSealedBlock.Header 173 }, 174 nil, 175 ) 176 177 findBlockByHeight := func(blocks map[flow.Identifier]*flow.Block, height uint64) (*flow.Block, bool) { 178 for _, block := range blocks { 179 if block.Header.Height == height { 180 return block, true 181 } 182 } 183 return nil, false 184 } 185 186 // define the protocol state snapshot for any block in `bc.Blocks` 187 bc.State.On("AtBlockID", mock.Anything).Return( 188 func(blockID flow.Identifier) realproto.Snapshot { 189 block, found := bc.Blocks[blockID] 190 if !found { 191 return StateSnapshotForUnknownBlock() 192 } 193 return StateSnapshotForKnownBlock(block.Header, bc.Identities) 194 }, 195 ) 196 197 bc.State.On("AtHeight", mock.Anything).Return( 198 func(height uint64) realproto.Snapshot { 199 block, found := findBlockByHeight(bc.Blocks, height) 200 if found { 201 snapshot := &protocol.Snapshot{} 202 snapshot.On("Head").Return( 203 func() *flow.Header { 204 return block.Header 205 }, 206 nil, 207 ) 208 return snapshot 209 } 210 panic(fmt.Sprintf("unknown height: %v, final: %v, sealed: %v", height, bc.LatestFinalizedBlock.Header.Height, bc.LatestSealedBlock.Header.Height)) 211 }, 212 ) 213 214 // ~~~~~~~~~~~~~~~~~~~~~~~ SETUP RESULTS STORAGE ~~~~~~~~~~~~~~~~~~~~~~~~ // 215 bc.PersistedResults = make(map[flow.Identifier]*flow.ExecutionResult) 216 bc.LatestExecutionResult = ExecutionResultFixture(WithBlock(&bc.LatestSealedBlock)) 217 bc.PersistedResults[bc.LatestExecutionResult.ID()] = bc.LatestExecutionResult 218 bc.ResultsDB = &storage.ExecutionResults{} 219 bc.ResultsDB.On("ByID", mock.Anything).Return( 220 func(resultID flow.Identifier) *flow.ExecutionResult { 221 return bc.PersistedResults[resultID] 222 }, 223 func(resultID flow.Identifier) error { 224 _, found := bc.PersistedResults[resultID] 225 if !found { 226 return storerr.ErrNotFound 227 } 228 return nil 229 }, 230 ).Maybe() 231 bc.ResultsDB.On("Store", mock.Anything).Return( 232 func(result *flow.ExecutionResult) error { 233 _, found := bc.PersistedResults[result.ID()] 234 if found { 235 return storerr.ErrAlreadyExists 236 } 237 return nil 238 }, 239 ).Maybe() // this call is optional 240 // ~~~~~~~~~~~~~~~~~~~~~~~ SETUP RECEIPTS STORAGE ~~~~~~~~~~~~~~~~~~~~~~~~ // 241 bc.ReceiptsDB = &storage.ExecutionReceipts{} 242 243 // ~~~~~~~~~~~~~~~~~~~~ SETUP BLOCK HEADER STORAGE ~~~~~~~~~~~~~~~~~~~~~ // 244 bc.HeadersDB = &storage.Headers{} 245 bc.HeadersDB.On("ByBlockID", mock.Anything).Return( 246 func(blockID flow.Identifier) *flow.Header { 247 block, found := bc.Blocks[blockID] 248 if !found { 249 return nil 250 } 251 return block.Header 252 }, 253 func(blockID flow.Identifier) error { 254 _, found := bc.Blocks[blockID] 255 if !found { 256 return storerr.ErrNotFound 257 } 258 return nil 259 }, 260 ) 261 bc.HeadersDB.On("ByHeight", mock.Anything).Return( 262 func(blockHeight uint64) *flow.Header { 263 for _, b := range bc.Blocks { 264 if b.Header.Height == blockHeight { 265 return b.Header 266 } 267 } 268 return nil 269 }, 270 func(blockHeight uint64) error { 271 for _, b := range bc.Blocks { 272 if b.Header.Height == blockHeight { 273 return nil 274 } 275 } 276 return storerr.ErrNotFound 277 }, 278 ) 279 280 // ~~~~~~~~~~~~~~~~~~~~ SETUP BLOCK PAYLOAD STORAGE ~~~~~~~~~~~~~~~~~~~~~ // 281 bc.IndexDB = &storage.Index{} 282 bc.IndexDB.On("ByBlockID", mock.Anything).Return( 283 func(blockID flow.Identifier) *flow.Index { 284 block, found := bc.Blocks[blockID] 285 if !found { 286 return nil 287 } 288 if block.Payload == nil { 289 return nil 290 } 291 return block.Payload.Index() 292 }, 293 func(blockID flow.Identifier) error { 294 block, found := bc.Blocks[blockID] 295 if !found { 296 return storerr.ErrNotFound 297 } 298 if block.Payload == nil { 299 return storerr.ErrNotFound 300 } 301 return nil 302 }, 303 ) 304 305 bc.SealsIndex = make(map[flow.Identifier]*flow.Seal) 306 firtSeal := Seal.Fixture(Seal.WithBlock(bc.LatestSealedBlock.Header), 307 Seal.WithResult(bc.LatestExecutionResult)) 308 for id, block := range bc.Blocks { 309 if id != bc.RootBlock.ID() { 310 bc.SealsIndex[block.ID()] = firtSeal 311 } 312 } 313 314 bc.PayloadsDB = &storage.Payloads{} 315 bc.PayloadsDB.On("ByBlockID", mock.Anything).Return( 316 func(blockID flow.Identifier) *flow.Payload { 317 block, found := bc.Blocks[blockID] 318 if !found { 319 return nil 320 } 321 if block.Payload == nil { 322 return nil 323 } 324 return block.Payload 325 }, 326 func(blockID flow.Identifier) error { 327 block, found := bc.Blocks[blockID] 328 if !found { 329 return storerr.ErrNotFound 330 } 331 if block.Payload == nil { 332 return storerr.ErrNotFound 333 } 334 return nil 335 }, 336 ) 337 338 bc.SealsDB = &storage.Seals{} 339 bc.SealsDB.On("HighestInFork", mock.Anything).Return( 340 func(blockID flow.Identifier) *flow.Seal { 341 seal, found := bc.SealsIndex[blockID] 342 if !found { 343 return nil 344 } 345 return seal 346 }, 347 func(blockID flow.Identifier) error { 348 seal, found := bc.SealsIndex[blockID] 349 if !found { 350 return storerr.ErrNotFound 351 } 352 if seal == nil { 353 return storerr.ErrNotFound 354 } 355 return nil 356 }, 357 ) 358 359 // ~~~~~~~~~~~~~~~~~~~~~~~ SETUP RECEIPTS MEMPOOL ~~~~~~~~~~~~~~~~~~~~~~ // 360 bc.ReceiptsPL = &mempool.ExecutionTree{} 361 bc.ReceiptsPL.On("Size").Return(uint(0)).Maybe() // only for metrics 362 bc.ReceiptsPL.On("HasReceipt", mock.AnythingOfType("*flow.ExecutionReceipt")).Return(false) 363 364 bc.PendingReceipts = &mempool.PendingReceipts{} 365 366 // ~~~~~~~~~~~~~~~~~~~~~~~~ SETUP SEALS MEMPOOL ~~~~~~~~~~~~~~~~~~~~~~~~ // 367 bc.PendingSeals = make(map[flow.Identifier]*flow.IncorporatedResultSeal) 368 bc.SealsPL = &mempool.IncorporatedResultSeals{} 369 bc.SealsPL.On("Size").Return(uint(0)).Maybe() // only for metrics 370 bc.SealsPL.On("Limit").Return(uint(1000)).Maybe() 371 bc.SealsPL.On("ByID", mock.Anything).Return( 372 func(sealID flow.Identifier) *flow.IncorporatedResultSeal { 373 return bc.PendingSeals[sealID] 374 }, 375 func(sealID flow.Identifier) bool { 376 _, found := bc.PendingSeals[sealID] 377 return found 378 }, 379 ).Maybe() 380 bc.SealsPL.On("All").Return( 381 func() []*flow.IncorporatedResultSeal { 382 seals := make([]*flow.IncorporatedResultSeal, 0, len(bc.PendingSeals)) 383 for _, seal := range bc.PendingSeals { 384 seals = append(seals, seal) 385 } 386 return seals 387 }, 388 ).Maybe() 389 390 bc.Assigner = &module.ChunkAssigner{} 391 bc.Assignments = make(map[flow.Identifier]*chunks.Assignment) 392 } 393 394 func StateSnapshotForUnknownBlock() *protocol.Snapshot { 395 snapshot := &protocol.Snapshot{} 396 snapshot.On("Identity", mock.Anything).Return( 397 nil, state.ErrUnknownSnapshotReference, 398 ) 399 snapshot.On("Identities", mock.Anything).Return( 400 nil, state.ErrUnknownSnapshotReference, 401 ) 402 snapshot.On("Head", mock.Anything).Return( 403 nil, state.ErrUnknownSnapshotReference, 404 ) 405 return snapshot 406 } 407 408 func StateSnapshotForKnownBlock(block *flow.Header, identities map[flow.Identifier]*flow.Identity) *protocol.Snapshot { 409 snapshot := &protocol.Snapshot{} 410 snapshot.On("Identity", mock.Anything).Return( 411 func(nodeID flow.Identifier) *flow.Identity { 412 return identities[nodeID] 413 }, 414 func(nodeID flow.Identifier) error { 415 _, found := identities[nodeID] 416 if !found { 417 return realproto.IdentityNotFoundError{NodeID: nodeID} 418 } 419 return nil 420 }, 421 ) 422 snapshot.On("Identities", mock.Anything).Return( 423 func(selector flow.IdentityFilter[flow.Identity]) flow.IdentityList { 424 var idts flow.IdentityList 425 for _, i := range identities { 426 if selector(i) { 427 idts = append(idts, i) 428 } 429 } 430 return idts 431 }, 432 func(selector flow.IdentityFilter[flow.Identity]) error { 433 return nil 434 }, 435 ) 436 snapshot.On("Head").Return(block, nil) 437 return snapshot 438 } 439 440 func ApprovalFor(result *flow.ExecutionResult, chunkIdx uint64, approverID flow.Identifier) *flow.ResultApproval { 441 return ResultApprovalFixture( 442 WithBlockID(result.BlockID), 443 WithExecutionResultID(result.ID()), 444 WithApproverID(approverID), 445 WithChunk(chunkIdx), 446 ) 447 } 448 449 func EntityWithID(expectedID flow.Identifier) interface{} { 450 return mock.MatchedBy( 451 func(entity flow.Entity) bool { 452 return expectedID == entity.ID() 453 }) 454 } 455 456 // subgraphFixture represents a subgraph of the blockchain: 457 // 458 // Result -----------------------------------> Block 459 // | | 460 // | v 461 // | ParentBlock 462 // v 463 // PreviousResult ---> PreviousResult.BlockID 464 // 465 // Depending on validity of the subgraph: 466 // - valid: PreviousResult.BlockID == ParentBlock.ID() 467 // - invalid: PreviousResult.BlockID != ParentBlock.ID() 468 type subgraphFixture struct { 469 Block *flow.Block 470 ParentBlock *flow.Block 471 Result *flow.ExecutionResult 472 PreviousResult *flow.ExecutionResult 473 IncorporatedResult *flow.IncorporatedResult 474 Assignment *chunks.Assignment 475 Approvals map[uint64]map[flow.Identifier]*flow.ResultApproval // chunkIndex -> Verifier Node ID -> Approval 476 } 477 478 // Generates a valid subgraph: 479 // let 480 // - R1 be a result which pertains to blockA 481 // - R2 be R1's previous result, 482 // where R2 pertains to blockB 483 // 484 // The execution results form a valid subgraph if and only if: 485 // 486 // blockA.ParentID == blockB.ID 487 func (bc *BaseChainSuite) ValidSubgraphFixture() subgraphFixture { 488 // BLOCKS: <- previousBlock <- block 489 parentBlock := BlockFixture() 490 parentBlock.SetPayload(PayloadFixture(WithGuarantees(CollectionGuaranteesFixture(12)...))) 491 block := BlockWithParentFixture(parentBlock.Header) 492 block.SetPayload(PayloadFixture(WithGuarantees(CollectionGuaranteesFixture(12)...))) 493 494 // RESULTS for Blocks: 495 previousResult := ExecutionResultFixture(WithBlock(&parentBlock)) 496 result := ExecutionResultFixture( 497 WithBlock(block), 498 WithPreviousResult(*previousResult), 499 ) 500 501 // Exec Receipt for block with valid subgraph 502 incorporatedResult := IncorporatedResult.Fixture(IncorporatedResult.WithResult(result)) 503 504 // assign each chunk to 50% of validation Nodes and generate respective approvals 505 assignment := chunks.NewAssignment() 506 assignedVerifiersPerChunk := uint(len(bc.Approvers) / 2) 507 approvals := make(map[uint64]map[flow.Identifier]*flow.ResultApproval) 508 for _, chunk := range incorporatedResult.Result.Chunks { 509 assignedVerifiers, err := bc.Approvers.Sample(assignedVerifiersPerChunk) 510 require.NoError(bc.T(), err) 511 assignment.Add(chunk, assignedVerifiers.NodeIDs()) 512 513 // generate approvals 514 chunkApprovals := make(map[flow.Identifier]*flow.ResultApproval) 515 for _, approver := range assignedVerifiers { 516 chunkApprovals[approver.NodeID] = ApprovalFor(incorporatedResult.Result, chunk.Index, approver.NodeID) 517 } 518 approvals[chunk.Index] = chunkApprovals 519 } 520 521 return subgraphFixture{ 522 Block: block, 523 ParentBlock: &parentBlock, 524 Result: result, 525 PreviousResult: previousResult, 526 IncorporatedResult: incorporatedResult, 527 Assignment: assignment, 528 Approvals: approvals, 529 } 530 } 531 532 func (bc *BaseChainSuite) Extend(block *flow.Block) { 533 blockID := block.ID() 534 bc.Blocks[blockID] = block 535 if seal, ok := bc.SealsIndex[block.Header.ParentID]; ok { 536 bc.SealsIndex[block.ID()] = seal 537 } 538 539 for _, result := range block.Payload.Results { 540 // Exec Receipt for block with valid subgraph 541 incorporatedResult := IncorporatedResult.Fixture(IncorporatedResult.WithResult(result), 542 IncorporatedResult.WithIncorporatedBlockID(blockID)) 543 544 // assign each chunk to 50% of validation Nodes and generate respective approvals 545 assignment := chunks.NewAssignment() 546 assignedVerifiersPerChunk := uint(len(bc.Approvers) / 2) 547 approvals := make(map[uint64]map[flow.Identifier]*flow.ResultApproval) 548 for _, chunk := range incorporatedResult.Result.Chunks { 549 assignedVerifiers, err := bc.Approvers.Sample(assignedVerifiersPerChunk) 550 require.NoError(bc.T(), err) 551 assignment.Add(chunk, assignedVerifiers.NodeIDs()) 552 553 // generate approvals 554 chunkApprovals := make(map[flow.Identifier]*flow.ResultApproval) 555 for _, approver := range assignedVerifiers { 556 chunkApprovals[approver.NodeID] = ApprovalFor(incorporatedResult.Result, chunk.Index, approver.NodeID) 557 } 558 approvals[chunk.Index] = chunkApprovals 559 } 560 bc.Assigner.On("Assign", incorporatedResult.Result, incorporatedResult.IncorporatedBlockID).Return(assignment, nil).Maybe() 561 bc.Assignments[incorporatedResult.Result.ID()] = assignment 562 bc.PersistedResults[result.ID()] = result 563 } 564 for _, seal := range block.Payload.Seals { 565 bc.SealsIndex[blockID] = seal 566 } 567 } 568 569 // addSubgraphFixtureToMempools adds add entities in subgraph to mempools and persistent storage mocks 570 func (bc *BaseChainSuite) AddSubgraphFixtureToMempools(subgraph subgraphFixture) { 571 bc.Blocks[subgraph.ParentBlock.ID()] = subgraph.ParentBlock 572 bc.Blocks[subgraph.Block.ID()] = subgraph.Block 573 bc.PersistedResults[subgraph.PreviousResult.ID()] = subgraph.PreviousResult 574 bc.PersistedResults[subgraph.Result.ID()] = subgraph.Result 575 bc.Assigner.On("Assign", subgraph.IncorporatedResult.Result, subgraph.IncorporatedResult.IncorporatedBlockID).Return(subgraph.Assignment, nil).Maybe() 576 }