github.com/koko1123/flow-go-1@v0.29.6/engine/access/access_test.go (about) 1 package access_test 2 3 import ( 4 "context" 5 "encoding/json" 6 "os" 7 "testing" 8 9 "github.com/dgraph-io/badger/v3" 10 "github.com/google/go-cmp/cmp" 11 accessproto "github.com/onflow/flow/protobuf/go/flow/access" 12 entitiesproto "github.com/onflow/flow/protobuf/go/flow/entities" 13 execproto "github.com/onflow/flow/protobuf/go/flow/execution" 14 "github.com/rs/zerolog" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/mock" 17 "github.com/stretchr/testify/require" 18 "github.com/stretchr/testify/suite" 19 "google.golang.org/protobuf/testing/protocmp" 20 21 "github.com/koko1123/flow-go-1/access" 22 hsmock "github.com/koko1123/flow-go-1/consensus/hotstuff/mocks" 23 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 24 "github.com/onflow/flow-go/crypto" 25 "github.com/koko1123/flow-go-1/engine/access/ingestion" 26 accessmock "github.com/koko1123/flow-go-1/engine/access/mock" 27 "github.com/koko1123/flow-go-1/engine/access/rpc" 28 "github.com/koko1123/flow-go-1/engine/access/rpc/backend" 29 factorymock "github.com/koko1123/flow-go-1/engine/access/rpc/backend/mock" 30 "github.com/koko1123/flow-go-1/engine/common/rpc/convert" 31 "github.com/koko1123/flow-go-1/model/flow" 32 "github.com/koko1123/flow-go-1/model/flow/factory" 33 "github.com/koko1123/flow-go-1/model/flow/filter" 34 "github.com/koko1123/flow-go-1/module/irrecoverable" 35 "github.com/koko1123/flow-go-1/module/mempool/stdmap" 36 "github.com/koko1123/flow-go-1/module/metrics" 37 module "github.com/koko1123/flow-go-1/module/mock" 38 "github.com/koko1123/flow-go-1/module/signature" 39 "github.com/koko1123/flow-go-1/network/channels" 40 "github.com/koko1123/flow-go-1/network/mocknetwork" 41 protocol "github.com/koko1123/flow-go-1/state/protocol/mock" 42 storage "github.com/koko1123/flow-go-1/storage/badger" 43 "github.com/koko1123/flow-go-1/storage/badger/operation" 44 "github.com/koko1123/flow-go-1/storage/util" 45 "github.com/koko1123/flow-go-1/utils/unittest" 46 ) 47 48 type Suite struct { 49 suite.Suite 50 state *protocol.State 51 snapshot *protocol.Snapshot 52 epochQuery *protocol.EpochQuery 53 signerIndicesDecoder *hsmock.BlockSignerDecoder 54 signerIds flow.IdentifierList 55 log zerolog.Logger 56 net *mocknetwork.Network 57 request *module.Requester 58 collClient *accessmock.AccessAPIClient 59 execClient *accessmock.ExecutionAPIClient 60 me *module.Local 61 chainID flow.ChainID 62 metrics *metrics.NoopCollector 63 backend *backend.Backend 64 } 65 66 // TestAccess tests scenarios which exercise multiple API calls using both the RPC handler and the ingest engine 67 // and using a real badger storage 68 func TestAccess(t *testing.T) { 69 suite.Run(t, new(Suite)) 70 } 71 72 func (suite *Suite) SetupTest() { 73 suite.log = zerolog.New(os.Stderr) 74 suite.net = new(mocknetwork.Network) 75 suite.state = new(protocol.State) 76 suite.snapshot = new(protocol.Snapshot) 77 78 suite.epochQuery = new(protocol.EpochQuery) 79 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 80 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 81 suite.snapshot.On("Epochs").Return(suite.epochQuery).Maybe() 82 83 header := unittest.BlockHeaderFixture() 84 params := new(protocol.Params) 85 params.On("Root").Return(header, nil) 86 suite.state.On("Params").Return(params).Maybe() 87 suite.collClient = new(accessmock.AccessAPIClient) 88 suite.execClient = new(accessmock.ExecutionAPIClient) 89 90 suite.request = new(module.Requester) 91 suite.request.On("EntityByID", mock.Anything, mock.Anything) 92 93 suite.me = new(module.Local) 94 95 suite.signerIds = unittest.IdentifierListFixture(4) 96 suite.signerIndicesDecoder = new(hsmock.BlockSignerDecoder) 97 suite.signerIndicesDecoder.On("DecodeSignerIDs", mock.Anything).Return(suite.signerIds, nil).Maybe() 98 99 accessIdentity := unittest.IdentityFixture(unittest.WithRole(flow.RoleAccess)) 100 suite.me. 101 On("NodeID"). 102 Return(accessIdentity.NodeID) 103 104 suite.chainID = flow.Testnet 105 suite.metrics = metrics.NewNoopCollector() 106 } 107 108 func (suite *Suite) RunTest( 109 f func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, headers *storage.Headers, results *storage.ExecutionResults), 110 ) { 111 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 112 headers, _, _, _, _, blocks, _, _, _, results := util.StorageLayer(suite.T(), db) 113 transactions := storage.NewTransactions(suite.metrics, db) 114 collections := storage.NewCollections(db, transactions) 115 receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize) 116 117 suite.backend = backend.New(suite.state, 118 suite.collClient, 119 nil, 120 blocks, 121 headers, 122 collections, 123 transactions, 124 receipts, 125 results, 126 suite.chainID, 127 suite.metrics, 128 nil, 129 false, 130 backend.DefaultMaxHeightRange, 131 nil, 132 nil, 133 suite.log, 134 backend.DefaultSnapshotHistoryLimit, 135 ) 136 137 handler := access.NewHandler(suite.backend, suite.chainID.Chain(), access.WithBlockSignerDecoder(suite.signerIndicesDecoder)) 138 f(handler, db, blocks, headers, results) 139 }) 140 } 141 142 func (suite *Suite) TestSendAndGetTransaction() { 143 suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) { 144 referenceBlock := unittest.BlockHeaderFixture() 145 transaction := unittest.TransactionFixture() 146 transaction.SetReferenceBlockID(referenceBlock.ID()) 147 148 refSnapshot := new(protocol.Snapshot) 149 150 suite.state. 151 On("AtBlockID", referenceBlock.ID()). 152 Return(refSnapshot, nil) 153 154 refSnapshot. 155 On("Head"). 156 Return(referenceBlock, nil). 157 Twice() 158 159 suite.snapshot. 160 On("Head"). 161 Return(referenceBlock, nil). 162 Once() 163 164 expected := convert.TransactionToMessage(transaction.TransactionBody) 165 sendReq := &accessproto.SendTransactionRequest{ 166 Transaction: expected, 167 } 168 sendResp := accessproto.SendTransactionResponse{} 169 170 suite.collClient. 171 On("SendTransaction", mock.Anything, mock.Anything). 172 Return(&sendResp, nil). 173 Once() 174 175 // Send transaction 176 resp, err := handler.SendTransaction(context.Background(), sendReq) 177 suite.Require().NoError(err) 178 suite.Require().NotNil(resp) 179 180 id := transaction.ID() 181 getReq := &accessproto.GetTransactionRequest{ 182 Id: id[:], 183 } 184 185 // Get transaction 186 gResp, err := handler.GetTransaction(context.Background(), getReq) 187 suite.Require().NoError(err) 188 suite.Require().NotNil(gResp) 189 190 actual := gResp.GetTransaction() 191 suite.Require().Equal(expected, actual) 192 }) 193 } 194 195 func (suite *Suite) TestSendExpiredTransaction() { 196 suite.RunTest(func(handler *access.Handler, _ *badger.DB, _ *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) { 197 referenceBlock := unittest.BlockHeaderFixture() 198 199 // create latest block that is past the expiry window 200 latestBlock := unittest.BlockHeaderFixture() 201 latestBlock.Height = referenceBlock.Height + flow.DefaultTransactionExpiry*2 202 203 transaction := unittest.TransactionFixture() 204 transaction.SetReferenceBlockID(referenceBlock.ID()) 205 206 refSnapshot := new(protocol.Snapshot) 207 208 suite.state. 209 On("AtBlockID", referenceBlock.ID()). 210 Return(refSnapshot, nil) 211 212 refSnapshot. 213 On("Head"). 214 Return(referenceBlock, nil). 215 Twice() 216 217 suite.snapshot. 218 On("Head"). 219 Return(latestBlock, nil). 220 Once() 221 222 req := &accessproto.SendTransactionRequest{ 223 Transaction: convert.TransactionToMessage(transaction.TransactionBody), 224 } 225 226 _, err := handler.SendTransaction(context.Background(), req) 227 suite.Require().Error(err) 228 }) 229 } 230 231 type mockCloser struct{} 232 233 func (mc *mockCloser) Close() error { return nil } 234 235 // TestSendTransactionToRandomCollectionNode tests that collection nodes are chosen from the appropriate cluster when 236 // forwarding transactions by sending two transactions bound for two different collection clusters. 237 func (suite *Suite) TestSendTransactionToRandomCollectionNode() { 238 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 239 240 // create a transaction 241 referenceBlock := unittest.BlockHeaderFixture() 242 transaction := unittest.TransactionFixture() 243 transaction.SetReferenceBlockID(referenceBlock.ID()) 244 245 // setup the state and snapshot mock expectations 246 suite.state.On("AtBlockID", referenceBlock.ID()).Return(suite.snapshot, nil) 247 suite.snapshot.On("Head").Return(referenceBlock, nil) 248 249 // create storage 250 metrics := metrics.NewNoopCollector() 251 transactions := storage.NewTransactions(metrics, db) 252 collections := storage.NewCollections(db, transactions) 253 254 // create collection node cluster 255 count := 2 256 collNodes := unittest.IdentityListFixture(count, unittest.WithRole(flow.RoleCollection)) 257 assignments := unittest.ClusterAssignment(uint(count), collNodes) 258 clusters, err := factory.NewClusterList(assignments, collNodes) 259 suite.Require().Nil(err) 260 collNode1 := clusters[0][0] 261 collNode2 := clusters[1][0] 262 epoch := new(protocol.Epoch) 263 suite.epochQuery.On("Current").Return(epoch) 264 epoch.On("Clustering").Return(clusters, nil) 265 266 // create two transactions bound for each of the cluster 267 cluster1 := clusters[0] 268 cluster1tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster1, func(transaction *flow.TransactionBody) {}) 269 tx1 := convert.TransactionToMessage(cluster1tx) 270 sendReq1 := &accessproto.SendTransactionRequest{ 271 Transaction: tx1, 272 } 273 cluster2 := clusters[1] 274 cluster2tx := unittest.AlterTransactionForCluster(transaction.TransactionBody, clusters, cluster2, func(transaction *flow.TransactionBody) {}) 275 tx2 := convert.TransactionToMessage(cluster2tx) 276 sendReq2 := &accessproto.SendTransactionRequest{ 277 Transaction: tx2, 278 } 279 sendResp := accessproto.SendTransactionResponse{} 280 281 // create mock access api clients for each of the collection node expecting the correct transaction once 282 col1ApiClient := new(accessmock.AccessAPIClient) 283 col1ApiClient.On("SendTransaction", mock.Anything, sendReq1).Return(&sendResp, nil).Once() 284 col2ApiClient := new(accessmock.AccessAPIClient) 285 col2ApiClient.On("SendTransaction", mock.Anything, sendReq2).Return(&sendResp, nil).Once() 286 287 // create a mock connection factory 288 connFactory := new(factorymock.ConnectionFactory) 289 connFactory.On("GetAccessAPIClient", collNode1.Address).Return(col1ApiClient, &mockCloser{}, nil) 290 connFactory.On("GetAccessAPIClient", collNode2.Address).Return(col2ApiClient, &mockCloser{}, nil) 291 292 backend := backend.New(suite.state, 293 nil, 294 nil, 295 nil, 296 nil, 297 collections, 298 transactions, 299 nil, 300 nil, 301 suite.chainID, 302 metrics, 303 connFactory, 304 false, 305 backend.DefaultMaxHeightRange, 306 nil, 307 nil, 308 suite.log, 309 backend.DefaultSnapshotHistoryLimit, 310 ) 311 312 handler := access.NewHandler(backend, suite.chainID.Chain()) 313 314 // Send transaction 1 315 resp, err := handler.SendTransaction(context.Background(), sendReq1) 316 require.NoError(suite.T(), err) 317 require.NotNil(suite.T(), resp) 318 319 // Send transaction 2 320 resp, err = handler.SendTransaction(context.Background(), sendReq2) 321 require.NoError(suite.T(), err) 322 require.NotNil(suite.T(), resp) 323 324 // verify that a collection node in the correct cluster was contacted exactly once 325 col1ApiClient.AssertExpectations(suite.T()) 326 col2ApiClient.AssertExpectations(suite.T()) 327 epoch.AssertNumberOfCalls(suite.T(), "Clustering", 2) 328 329 // additionally do a GetTransaction request for the two transactions 330 getTx := func(tx flow.TransactionBody) { 331 id := tx.ID() 332 getReq := &accessproto.GetTransactionRequest{ 333 Id: id[:], 334 } 335 gResp, err := handler.GetTransaction(context.Background(), getReq) 336 require.NoError(suite.T(), err) 337 require.NotNil(suite.T(), gResp) 338 actual := gResp.GetTransaction() 339 expected := convert.TransactionToMessage(tx) 340 require.Equal(suite.T(), expected, actual) 341 } 342 343 getTx(cluster1tx) 344 getTx(cluster1tx) 345 }) 346 } 347 348 func (suite *Suite) TestGetBlockByIDAndHeight() { 349 suite.RunTest(func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, _ *storage.Headers, _ *storage.ExecutionResults) { 350 351 // test block1 get by ID 352 block1 := unittest.BlockFixture() 353 // test block2 get by height 354 block2 := unittest.BlockFixture() 355 block2.Header.Height = 2 356 357 require.NoError(suite.T(), blocks.Store(&block1)) 358 require.NoError(suite.T(), blocks.Store(&block2)) 359 360 // the follower logic should update height index on the block storage when a block is finalized 361 err := db.Update(operation.IndexBlockHeight(block2.Header.Height, block2.ID())) 362 require.NoError(suite.T(), err) 363 364 assertHeaderResp := func(resp *accessproto.BlockHeaderResponse, err error, header *flow.Header) { 365 require.NoError(suite.T(), err) 366 require.NotNil(suite.T(), resp) 367 actual := resp.Block 368 expectedMessage, err := convert.BlockHeaderToMessage(header, suite.signerIds) 369 require.NoError(suite.T(), err) 370 require.Empty(suite.T(), cmp.Diff(expectedMessage, actual, protocmp.Transform())) 371 expectedBlockHeader, err := convert.MessageToBlockHeader(actual) 372 require.NoError(suite.T(), err) 373 require.Equal(suite.T(), expectedBlockHeader, header) 374 } 375 376 assertBlockResp := func(resp *accessproto.BlockResponse, err error, block *flow.Block) { 377 require.NoError(suite.T(), err) 378 require.NotNil(suite.T(), resp) 379 actual := resp.Block 380 expectedMessage, err := convert.BlockToMessage(block, suite.signerIds) 381 require.NoError(suite.T(), err) 382 require.Equal(suite.T(), expectedMessage, actual) 383 expectedBlock, err := convert.MessageToBlock(resp.Block) 384 require.NoError(suite.T(), err) 385 require.Equal(suite.T(), expectedBlock.ID(), block.ID()) 386 } 387 388 assertLightBlockResp := func(resp *accessproto.BlockResponse, err error, block *flow.Block) { 389 require.NoError(suite.T(), err) 390 require.NotNil(suite.T(), resp) 391 actual := resp.Block 392 expectedMessage := convert.BlockToMessageLight(block) 393 require.Equal(suite.T(), expectedMessage, actual) 394 } 395 396 suite.snapshot.On("Head").Return(block1.Header, nil) 397 suite.Run("get header 1 by ID", func() { 398 // get header by ID 399 id := block1.ID() 400 req := &accessproto.GetBlockHeaderByIDRequest{ 401 Id: id[:], 402 } 403 404 resp, err := handler.GetBlockHeaderByID(context.Background(), req) 405 406 // assert it is indeed block1 407 assertHeaderResp(resp, err, block1.Header) 408 }) 409 410 suite.Run("get block 1 by ID", func() { 411 id := block1.ID() 412 // get block details by ID 413 req := &accessproto.GetBlockByIDRequest{ 414 Id: id[:], 415 FullBlockResponse: true, 416 } 417 418 resp, err := handler.GetBlockByID(context.Background(), req) 419 420 assertBlockResp(resp, err, &block1) 421 }) 422 423 suite.Run("get block light 1 by ID", func() { 424 id := block1.ID() 425 // get block details by ID 426 req := &accessproto.GetBlockByIDRequest{ 427 Id: id[:], 428 } 429 430 resp, err := handler.GetBlockByID(context.Background(), req) 431 432 assertLightBlockResp(resp, err, &block1) 433 }) 434 435 suite.Run("get header 2 by height", func() { 436 437 // get header by height 438 req := &accessproto.GetBlockHeaderByHeightRequest{ 439 Height: block2.Header.Height, 440 } 441 442 resp, err := handler.GetBlockHeaderByHeight(context.Background(), req) 443 444 assertHeaderResp(resp, err, block2.Header) 445 }) 446 447 suite.Run("get block 2 by height", func() { 448 // get block details by height 449 req := &accessproto.GetBlockByHeightRequest{ 450 Height: block2.Header.Height, 451 FullBlockResponse: true, 452 } 453 454 resp, err := handler.GetBlockByHeight(context.Background(), req) 455 456 assertBlockResp(resp, err, &block2) 457 }) 458 459 suite.Run("get block 2 by height", func() { 460 // get block details by height 461 req := &accessproto.GetBlockByHeightRequest{ 462 Height: block2.Header.Height, 463 } 464 465 resp, err := handler.GetBlockByHeight(context.Background(), req) 466 467 assertLightBlockResp(resp, err, &block2) 468 }) 469 }) 470 } 471 472 func (suite *Suite) TestGetExecutionResultByBlockID() { 473 suite.RunTest(func(handler *access.Handler, db *badger.DB, blocks *storage.Blocks, _ *storage.Headers, executionResults *storage.ExecutionResults) { 474 475 // test block1 get by ID 476 nonexistingID := unittest.IdentifierFixture() 477 blockID := unittest.IdentifierFixture() 478 479 er := unittest.ExecutionResultFixture( 480 unittest.WithExecutionResultBlockID(blockID), 481 unittest.WithServiceEvents(2)) 482 483 require.NoError(suite.T(), executionResults.Store(er)) 484 require.NoError(suite.T(), executionResults.Index(blockID, er.ID())) 485 486 assertResp := func(resp *accessproto.ExecutionResultForBlockIDResponse, err error, executionResult *flow.ExecutionResult) { 487 require.NoError(suite.T(), err) 488 require.NotNil(suite.T(), resp) 489 er := resp.ExecutionResult 490 491 require.Len(suite.T(), er.Chunks, len(executionResult.Chunks)) 492 require.Len(suite.T(), er.ServiceEvents, len(executionResult.ServiceEvents)) 493 494 assert.Equal(suite.T(), executionResult.BlockID, convert.MessageToIdentifier(er.BlockId)) 495 assert.Equal(suite.T(), executionResult.PreviousResultID, convert.MessageToIdentifier(er.PreviousResultId)) 496 assert.Equal(suite.T(), executionResult.ExecutionDataID, convert.MessageToIdentifier(er.ExecutionDataId)) 497 498 for i, chunk := range executionResult.Chunks { 499 assert.Equal(suite.T(), chunk.BlockID[:], er.Chunks[i].BlockId) 500 assert.Equal(suite.T(), chunk.Index, er.Chunks[i].Index) 501 assert.Equal(suite.T(), uint32(chunk.CollectionIndex), er.Chunks[i].CollectionIndex) 502 assert.Equal(suite.T(), chunk.StartState[:], er.Chunks[i].StartState) 503 assert.Equal(suite.T(), chunk.EventCollection[:], er.Chunks[i].EventCollection) 504 assert.Equal(suite.T(), chunk.TotalComputationUsed, er.Chunks[i].TotalComputationUsed) 505 assert.Equal(suite.T(), uint32(chunk.NumberOfTransactions), er.Chunks[i].NumberOfTransactions) 506 assert.Equal(suite.T(), chunk.EndState[:], er.Chunks[i].EndState) 507 } 508 509 for i, serviceEvent := range executionResult.ServiceEvents { 510 assert.Equal(suite.T(), serviceEvent.Type, er.ServiceEvents[i].Type) 511 event := serviceEvent.Event 512 513 marshalledEvent, err := json.Marshal(event) 514 require.NoError(suite.T(), err) 515 516 assert.Equal(suite.T(), marshalledEvent, er.ServiceEvents[i].Payload) 517 } 518 parsedExecResult, err := convert.MessageToExecutionResult(resp.ExecutionResult) 519 require.NoError(suite.T(), err) 520 assert.Equal(suite.T(), parsedExecResult, executionResult) 521 assert.Equal(suite.T(), parsedExecResult.ID(), executionResult.ID()) 522 } 523 524 suite.Run("nonexisting block", func() { 525 req := &accessproto.GetExecutionResultForBlockIDRequest{ 526 BlockId: nonexistingID[:], 527 } 528 529 resp, err := handler.GetExecutionResultForBlockID(context.Background(), req) 530 531 require.Error(suite.T(), err) 532 require.Nil(suite.T(), resp) 533 }) 534 535 suite.Run("some block", func() { 536 // get header by ID 537 req := &accessproto.GetExecutionResultForBlockIDRequest{ 538 BlockId: blockID[:], 539 } 540 541 resp, err := handler.GetExecutionResultForBlockID(context.Background(), req) 542 543 require.NoError(suite.T(), err) 544 545 assertResp(resp, err, er) 546 }) 547 548 }) 549 } 550 551 // TestGetSealedTransaction tests that transactions status of transaction that belongs to a sealed block 552 // is reported as sealed 553 func (suite *Suite) TestGetSealedTransaction() { 554 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 555 headers, _, _, _, _, blocks, _, _, _, _ := util.StorageLayer(suite.T(), db) 556 results := storage.NewExecutionResults(suite.metrics, db) 557 receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize) 558 enIdentities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 559 enNodeIDs := flow.IdentifierList(enIdentities.NodeIDs()) 560 561 // create block -> collection -> transactions 562 block, collection := suite.createChain() 563 564 // setup mocks 565 originID := unittest.IdentifierFixture() 566 conduit := new(mocknetwork.Conduit) 567 suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil). 568 Once() 569 suite.request.On("Request", mock.Anything, mock.Anything).Return() 570 571 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 572 573 colIdentities := unittest.IdentityListFixture(1, unittest.WithRole(flow.RoleCollection)) 574 allIdentities := append(colIdentities, enIdentities...) 575 576 suite.snapshot.On("Identities", mock.Anything).Return(allIdentities, nil).Once() 577 578 exeEventResp := execproto.GetTransactionResultResponse{ 579 Events: nil, 580 } 581 582 // generate receipts 583 executionReceipts := unittest.ReceiptsForBlockFixture(&block, enNodeIDs) 584 585 // assume execution node returns an empty list of events 586 suite.execClient.On("GetTransactionResult", mock.Anything, mock.Anything).Return(&exeEventResp, nil) 587 588 // create a mock connection factory 589 connFactory := new(factorymock.ConnectionFactory) 590 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 591 592 // initialize storage 593 metrics := metrics.NewNoopCollector() 594 transactions := storage.NewTransactions(metrics, db) 595 collections := storage.NewCollections(db, transactions) 596 collectionsToMarkFinalized, err := stdmap.NewTimes(100) 597 require.NoError(suite.T(), err) 598 collectionsToMarkExecuted, err := stdmap.NewTimes(100) 599 require.NoError(suite.T(), err) 600 blocksToMarkExecuted, err := stdmap.NewTimes(100) 601 require.NoError(suite.T(), err) 602 603 backend := backend.New(suite.state, 604 suite.collClient, 605 nil, 606 blocks, 607 headers, 608 collections, 609 transactions, 610 receipts, 611 results, 612 suite.chainID, 613 suite.metrics, 614 connFactory, 615 false, 616 backend.DefaultMaxHeightRange, 617 nil, 618 enNodeIDs.Strings(), 619 suite.log, 620 backend.DefaultSnapshotHistoryLimit, 621 ) 622 623 handler := access.NewHandler(backend, suite.chainID.Chain()) 624 625 rpcEngBuilder, err := rpc.NewBuilder(suite.log, suite.state, rpc.Config{}, nil, nil, blocks, headers, collections, transactions, receipts, 626 results, suite.chainID, metrics, metrics, 0, 0, false, false, nil, nil) 627 require.NoError(suite.T(), err) 628 rpcEng, err := rpcEngBuilder.WithLegacy().Build() 629 require.NoError(suite.T(), err) 630 631 // create the ingest engine 632 ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, blocks, headers, collections, 633 transactions, results, receipts, metrics, collectionsToMarkFinalized, collectionsToMarkExecuted, blocksToMarkExecuted, rpcEng) 634 require.NoError(suite.T(), err) 635 636 // 1. Assume that follower engine updated the block storage and the protocol state. The block is reported as sealed 637 err = blocks.Store(&block) 638 require.NoError(suite.T(), err) 639 suite.snapshot.On("Head").Return(block.Header, nil).Twice() 640 641 background, cancel := context.WithCancel(context.Background()) 642 defer cancel() 643 644 ctx, _ := irrecoverable.WithSignaler(background) 645 ingestEng.Start(ctx) 646 <-ingestEng.Ready() 647 648 // 2. Ingest engine was notified by the follower engine about a new block. 649 // Follower engine --> Ingest engine 650 mb := &model.Block{ 651 BlockID: block.ID(), 652 } 653 ingestEng.OnFinalizedBlock(mb) 654 655 // 3. Request engine is used to request missing collection 656 suite.request.On("EntityByID", collection.ID(), mock.Anything).Return() 657 658 // 4. Ingest engine receives the requested collection and all the execution receipts 659 ingestEng.OnCollection(originID, &collection) 660 661 for _, r := range executionReceipts { 662 err = ingestEng.Process(channels.ReceiveReceipts, enNodeIDs[0], r) 663 require.NoError(suite.T(), err) 664 } 665 666 // 5. Client requests a transaction 667 tx := collection.Transactions[0] 668 txID := tx.ID() 669 getReq := &accessproto.GetTransactionRequest{ 670 Id: txID[:], 671 } 672 gResp, err := handler.GetTransactionResult(context.Background(), getReq) 673 require.NoError(suite.T(), err) 674 // assert that the transaction is reported as Sealed 675 require.Equal(suite.T(), entitiesproto.TransactionStatus_SEALED, gResp.GetStatus()) 676 }) 677 } 678 679 // TestExecuteScript tests the three execute Script related calls to make sure that the execution api is called with 680 // the correct block id 681 func (suite *Suite) TestExecuteScript() { 682 unittest.RunWithBadgerDB(suite.T(), func(db *badger.DB) { 683 headers, _, _, _, _, blocks, _, _, _, _ := util.StorageLayer(suite.T(), db) 684 transactions := storage.NewTransactions(suite.metrics, db) 685 collections := storage.NewCollections(db, transactions) 686 results := storage.NewExecutionResults(suite.metrics, db) 687 receipts := storage.NewExecutionReceipts(suite.metrics, db, results, storage.DefaultCacheSize) 688 689 identities := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 690 suite.snapshot.On("Identities", mock.Anything).Return(identities, nil) 691 692 // create a mock connection factory 693 connFactory := new(factorymock.ConnectionFactory) 694 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 695 696 suite.backend = backend.New(suite.state, 697 suite.collClient, 698 nil, 699 blocks, 700 headers, 701 collections, 702 transactions, 703 receipts, 704 results, 705 suite.chainID, 706 suite.metrics, 707 connFactory, 708 false, 709 backend.DefaultMaxHeightRange, 710 nil, 711 flow.IdentifierList(identities.NodeIDs()).Strings(), 712 suite.log, 713 backend.DefaultSnapshotHistoryLimit, 714 ) 715 716 handler := access.NewHandler(suite.backend, suite.chainID.Chain()) 717 718 // initialize metrics related storage 719 metrics := metrics.NewNoopCollector() 720 collectionsToMarkFinalized, err := stdmap.NewTimes(100) 721 require.NoError(suite.T(), err) 722 collectionsToMarkExecuted, err := stdmap.NewTimes(100) 723 require.NoError(suite.T(), err) 724 blocksToMarkExecuted, err := stdmap.NewTimes(100) 725 require.NoError(suite.T(), err) 726 727 conduit := new(mocknetwork.Conduit) 728 suite.net.On("Register", channels.ReceiveReceipts, mock.Anything).Return(conduit, nil). 729 Once() 730 // create the ingest engine 731 ingestEng, err := ingestion.New(suite.log, suite.net, suite.state, suite.me, suite.request, blocks, headers, collections, 732 transactions, results, receipts, metrics, collectionsToMarkFinalized, collectionsToMarkExecuted, blocksToMarkExecuted, nil) 733 require.NoError(suite.T(), err) 734 735 // create a block and a seal pointing to that block 736 lastBlock := unittest.BlockFixture() 737 lastBlock.Header.Height = 2 738 err = blocks.Store(&lastBlock) 739 require.NoError(suite.T(), err) 740 err = db.Update(operation.IndexBlockHeight(lastBlock.Header.Height, lastBlock.ID())) 741 require.NoError(suite.T(), err) 742 suite.snapshot.On("Head").Return(lastBlock.Header, nil).Once() 743 744 // create execution receipts for each of the execution node and the last block 745 executionReceipts := unittest.ReceiptsForBlockFixture(&lastBlock, identities.NodeIDs()) 746 // notify the ingest engine about the receipts 747 for _, r := range executionReceipts { 748 err = ingestEng.ProcessLocal(r) 749 require.NoError(suite.T(), err) 750 } 751 752 // create another block as a predecessor of the block created earlier 753 prevBlock := unittest.BlockFixture() 754 prevBlock.Header.Height = lastBlock.Header.Height - 1 755 err = blocks.Store(&prevBlock) 756 require.NoError(suite.T(), err) 757 err = db.Update(operation.IndexBlockHeight(prevBlock.Header.Height, prevBlock.ID())) 758 require.NoError(suite.T(), err) 759 760 // create execution receipts for each of the execution node and the previous block 761 executionReceipts = unittest.ReceiptsForBlockFixture(&prevBlock, identities.NodeIDs()) 762 // notify the ingest engine about the receipts 763 for _, r := range executionReceipts { 764 err = ingestEng.ProcessLocal(r) 765 require.NoError(suite.T(), err) 766 } 767 768 ctx := context.Background() 769 770 script := []byte("dummy script") 771 772 // setupExecClientMock sets up the mock the execution client and returns the access response to expect 773 setupExecClientMock := func(blockID flow.Identifier) *accessproto.ExecuteScriptResponse { 774 id := blockID[:] 775 executionReq := execproto.ExecuteScriptAtBlockIDRequest{ 776 BlockId: id, 777 Script: script, 778 } 779 executionResp := execproto.ExecuteScriptAtBlockIDResponse{ 780 Value: []byte{9, 10, 11}, 781 } 782 783 suite.execClient.On("ExecuteScriptAtBlockID", ctx, &executionReq).Return(&executionResp, nil).Once() 784 785 expectedResp := accessproto.ExecuteScriptResponse{ 786 Value: executionResp.GetValue(), 787 } 788 return &expectedResp 789 } 790 791 assertResult := func(err error, expected interface{}, actual interface{}) { 792 suite.Require().NoError(err) 793 suite.Require().Equal(expected, actual) 794 suite.execClient.AssertExpectations(suite.T()) 795 } 796 797 suite.Run("execute script at latest block", func() { 798 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 799 suite.state. 800 On("AtBlockID", lastBlock.ID()). 801 Return(suite.snapshot, nil) 802 803 expectedResp := setupExecClientMock(lastBlock.ID()) 804 req := accessproto.ExecuteScriptAtLatestBlockRequest{ 805 Script: script, 806 } 807 actualResp, err := handler.ExecuteScriptAtLatestBlock(ctx, &req) 808 assertResult(err, expectedResp, actualResp) 809 }) 810 811 suite.Run("execute script at block id", func() { 812 suite.state. 813 On("AtBlockID", prevBlock.ID()). 814 Return(suite.snapshot, nil) 815 816 expectedResp := setupExecClientMock(prevBlock.ID()) 817 id := prevBlock.ID() 818 req := accessproto.ExecuteScriptAtBlockIDRequest{ 819 BlockId: id[:], 820 Script: script, 821 } 822 actualResp, err := handler.ExecuteScriptAtBlockID(ctx, &req) 823 assertResult(err, expectedResp, actualResp) 824 }) 825 826 suite.Run("execute script at block height", func() { 827 suite.state. 828 On("AtBlockID", prevBlock.ID()). 829 Return(suite.snapshot, nil) 830 831 expectedResp := setupExecClientMock(prevBlock.ID()) 832 req := accessproto.ExecuteScriptAtBlockHeightRequest{ 833 BlockHeight: prevBlock.Header.Height, 834 Script: script, 835 } 836 actualResp, err := handler.ExecuteScriptAtBlockHeight(ctx, &req) 837 assertResult(err, expectedResp, actualResp) 838 }) 839 }) 840 } 841 842 func (suite *Suite) createChain() (flow.Block, flow.Collection) { 843 collection := unittest.CollectionFixture(10) 844 refBlockID := unittest.IdentifierFixture() 845 // prepare cluster committee members 846 clusterCommittee := unittest.IdentityListFixture(32 * 4).Filter(filter.HasRole(flow.RoleCollection)) 847 // guarantee signers must be cluster committee members, so that access will fetch collection from 848 // the signers that are specified by guarantee.SignerIndices 849 indices, err := signature.EncodeSignersToIndices(clusterCommittee.NodeIDs(), clusterCommittee.NodeIDs()) 850 require.NoError(suite.T(), err) 851 guarantee := &flow.CollectionGuarantee{ 852 CollectionID: collection.ID(), 853 Signature: crypto.Signature([]byte("signature A")), 854 ReferenceBlockID: refBlockID, 855 SignerIndices: indices, 856 } 857 block := unittest.BlockFixture() 858 block.Payload.Guarantees = []*flow.CollectionGuarantee{guarantee} 859 block.Header.PayloadHash = block.Payload.Hash() 860 861 cluster := new(protocol.Cluster) 862 cluster.On("Members").Return(clusterCommittee, nil) 863 epoch := new(protocol.Epoch) 864 epoch.On("ClusterByChainID", mock.Anything).Return(cluster, nil) 865 epochs := new(protocol.EpochQuery) 866 epochs.On("Current").Return(epoch) 867 snap := new(protocol.Snapshot) 868 snap.On("Epochs").Return(epochs) 869 snap.On("SealingSegment").Return(&flow.SealingSegment{Blocks: unittest.BlockFixtures(2)}, nil).Maybe() 870 871 suite.state.On("AtBlockID", mock.Anything).Return(snap).Once() // initial height lookup in ingestion engine 872 suite.state.On("AtBlockID", refBlockID).Return(snap) 873 874 return block, collection 875 }