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  }