github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/backend_test.go (about) 1 package backend 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "testing" 8 "time" 9 10 "github.com/dgraph-io/badger/v3" 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/grpc/codes" 20 "google.golang.org/grpc/status" 21 22 access "github.com/koko1123/flow-go-1/engine/access/mock" 23 backendmock "github.com/koko1123/flow-go-1/engine/access/rpc/backend/mock" 24 "github.com/koko1123/flow-go-1/engine/common/rpc/convert" 25 "github.com/koko1123/flow-go-1/model/flow" 26 "github.com/koko1123/flow-go-1/module/metrics" 27 bprotocol "github.com/koko1123/flow-go-1/state/protocol/badger" 28 protocol "github.com/koko1123/flow-go-1/state/protocol/mock" 29 "github.com/koko1123/flow-go-1/state/protocol/util" 30 "github.com/koko1123/flow-go-1/storage" 31 storagemock "github.com/koko1123/flow-go-1/storage/mock" 32 "github.com/koko1123/flow-go-1/utils/unittest" 33 ) 34 35 type Suite struct { 36 suite.Suite 37 38 state *protocol.State 39 snapshot *protocol.Snapshot 40 log zerolog.Logger 41 42 blocks *storagemock.Blocks 43 headers *storagemock.Headers 44 collections *storagemock.Collections 45 transactions *storagemock.Transactions 46 receipts *storagemock.ExecutionReceipts 47 results *storagemock.ExecutionResults 48 colClient *access.AccessAPIClient 49 execClient *access.ExecutionAPIClient 50 historicalAccessClient *access.AccessAPIClient 51 connectionFactory *backendmock.ConnectionFactory 52 chainID flow.ChainID 53 } 54 55 func TestHandler(t *testing.T) { 56 suite.Run(t, new(Suite)) 57 } 58 59 func (suite *Suite) SetupTest() { 60 rand.Seed(time.Now().UnixNano()) 61 suite.log = zerolog.New(zerolog.NewConsoleWriter()) 62 suite.state = new(protocol.State) 63 suite.snapshot = new(protocol.Snapshot) 64 header := unittest.BlockHeaderFixture() 65 params := new(protocol.Params) 66 params.On("Root").Return(header, nil) 67 suite.state.On("Params").Return(params).Maybe() 68 suite.blocks = new(storagemock.Blocks) 69 suite.headers = new(storagemock.Headers) 70 suite.transactions = new(storagemock.Transactions) 71 suite.collections = new(storagemock.Collections) 72 suite.receipts = new(storagemock.ExecutionReceipts) 73 suite.results = new(storagemock.ExecutionResults) 74 suite.colClient = new(access.AccessAPIClient) 75 suite.execClient = new(access.ExecutionAPIClient) 76 suite.chainID = flow.Testnet 77 suite.historicalAccessClient = new(access.AccessAPIClient) 78 suite.connectionFactory = new(backendmock.ConnectionFactory) 79 } 80 81 func (suite *Suite) TestPing() { 82 suite.colClient. 83 On("Ping", mock.Anything, &accessproto.PingRequest{}). 84 Return(&accessproto.PingResponse{}, nil) 85 86 suite.execClient. 87 On("Ping", mock.Anything, &execproto.PingRequest{}). 88 Return(&execproto.PingResponse{}, nil) 89 90 backend := New( 91 suite.state, 92 suite.colClient, 93 nil, 94 nil, 95 nil, 96 nil, 97 nil, 98 nil, 99 nil, 100 suite.chainID, 101 metrics.NewNoopCollector(), 102 nil, 103 false, 104 DefaultMaxHeightRange, 105 nil, 106 nil, 107 suite.log, 108 DefaultSnapshotHistoryLimit, 109 ) 110 111 err := backend.Ping(context.Background()) 112 113 suite.Require().NoError(err) 114 } 115 116 func (suite *Suite) TestGetLatestFinalizedBlockHeader() { 117 // setup the mocks 118 block := unittest.BlockHeaderFixture() 119 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 120 suite.snapshot.On("Head").Return(block, nil).Once() 121 suite.state.On("Sealed").Return(suite.snapshot, nil) 122 suite.snapshot.On("Head").Return(block, nil).Once() 123 124 backend := New( 125 suite.state, 126 nil, 127 nil, 128 nil, 129 nil, 130 nil, 131 nil, 132 nil, 133 nil, 134 suite.chainID, 135 metrics.NewNoopCollector(), 136 nil, 137 false, 138 DefaultMaxHeightRange, 139 nil, 140 nil, 141 suite.log, 142 DefaultSnapshotHistoryLimit, 143 ) 144 145 // query the handler for the latest finalized block 146 header, status, err := backend.GetLatestBlockHeader(context.Background(), false) 147 suite.checkResponse(header, err) 148 149 // make sure we got the latest block 150 suite.Require().Equal(block.ID(), header.ID()) 151 suite.Require().Equal(block.Height, header.Height) 152 suite.Require().Equal(block.ParentID, header.ParentID) 153 suite.Require().Equal(status, flow.BlockStatusSealed) 154 155 suite.assertAllExpectations() 156 157 } 158 159 // TestGetLatestProtocolStateSnapshot_NoTransitionSpan tests our GetLatestProtocolStateSnapshot RPC endpoint 160 // where the sealing segment for the state requested at latest finalized block does not contain any blocks that 161 // spans an epoch or epoch phase transition. 162 func (suite *Suite) TestGetLatestProtocolStateSnapshot_NoTransitionSpan() { 163 identities := unittest.CompleteIdentitySet() 164 rootSnapshot := unittest.RootSnapshotFixture(identities) 165 util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.MutableState) { 166 epochBuilder := unittest.NewEpochBuilder(suite.T(), state) 167 // build epoch 1 168 // blocks in current state 169 // P <- A(S_P-1) <- B(S_P) <- C(S_A) <- D(S_B) |setup| <- E(S_C) <- F(S_D) |commit| <- G(S_E) 170 epochBuilder. 171 BuildEpoch(). 172 CompleteEpoch() 173 174 // get heights of each phase in built epochs 175 epoch1, ok := epochBuilder.EpochHeights(1) 176 require.True(suite.T(), ok) 177 178 // setup AtBlockID mock returns for state 179 for _, height := range epoch1.Range() { 180 suite.state.On("AtHeight", height).Return(state.AtHeight(height)).Once() 181 } 182 183 // Take snapshot at height of block D (epoch1.heights[3]) for valid segment and valid snapshot 184 // where it's sealing segment is B <- C <- D 185 snap := state.AtHeight(epoch1.Range()[3]) 186 suite.state.On("Final").Return(snap).Once() 187 188 backend := New( 189 suite.state, 190 nil, 191 nil, 192 nil, 193 nil, 194 nil, 195 nil, 196 nil, 197 nil, 198 suite.chainID, 199 metrics.NewNoopCollector(), 200 nil, 201 false, 202 100, 203 nil, 204 nil, 205 suite.log, 206 DefaultSnapshotHistoryLimit, 207 ) 208 209 // query the handler for the latest finalized snapshot 210 bytes, err := backend.GetLatestProtocolStateSnapshot(context.Background()) 211 suite.Require().NoError(err) 212 213 // we expect the endpoint to return the snapshot at the same height we requested 214 // because it has a valid sealing segment with no blocks spanning an epoch or phase transition 215 expectedSnapshotBytes, err := convert.SnapshotToBytes(snap) 216 suite.Require().NoError(err) 217 suite.Require().Equal(expectedSnapshotBytes, bytes) 218 }) 219 } 220 221 // TestGetLatestProtocolStateSnapshot_TransitionSpans tests our GetLatestProtocolStateSnapshot RPC endpoint 222 // where the sealing segment for the state requested for latest finalized block contains a block that 223 // spans an epoch transition and blocks that span epoch phase transitions. 224 func (suite *Suite) TestGetLatestProtocolStateSnapshot_TransitionSpans() { 225 identities := unittest.CompleteIdentitySet() 226 rootSnapshot := unittest.RootSnapshotFixture(identities) 227 util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.MutableState) { 228 epochBuilder := unittest.NewEpochBuilder(suite.T(), state) 229 230 // building 2 epochs allows us to take a snapshot at a point in time where 231 // an epoch transition happens 232 epochBuilder. 233 BuildEpoch(). 234 CompleteEpoch() 235 236 epochBuilder. 237 BuildEpoch(). 238 CompleteEpoch() 239 240 // get heights of each phase in built epochs 241 epoch1, ok := epochBuilder.EpochHeights(1) 242 require.True(suite.T(), ok) 243 epoch2, ok := epochBuilder.EpochHeights(2) 244 require.True(suite.T(), ok) 245 246 // setup AtHeight mock returns for state 247 for _, height := range append(epoch1.Range(), epoch2.Range()...) { 248 suite.state.On("AtHeight", height).Return(state.AtHeight(height)) 249 } 250 251 // Take snapshot at height of the first block of epoch2, the sealing segment of this snapshot 252 // will have contain block spanning an epoch transition as well as an epoch phase transition. 253 // This will cause our GetLatestProtocolStateSnapshot func to return a snapshot 254 // at block with height 3, the first block of the staking phase of epoch1. 255 256 snap := state.AtHeight(epoch2.Range()[0]) 257 suite.state.On("Final").Return(snap).Once() 258 259 backend := New( 260 suite.state, 261 nil, 262 nil, 263 nil, 264 nil, 265 nil, 266 nil, 267 nil, 268 nil, 269 suite.chainID, 270 metrics.NewNoopCollector(), 271 nil, 272 false, 273 100, 274 nil, 275 nil, 276 suite.log, 277 DefaultSnapshotHistoryLimit, 278 ) 279 280 // query the handler for the latest finalized snapshot 281 bytes, err := backend.GetLatestProtocolStateSnapshot(context.Background()) 282 suite.Require().NoError(err) 283 fmt.Println() 284 285 // we expect the endpoint to return last valid snapshot which is the snapshot at block D (height 3) 286 expectedSnapshotBytes, err := convert.SnapshotToBytes(state.AtHeight(epoch1.Range()[3])) 287 suite.Require().NoError(err) 288 suite.Require().Equal(expectedSnapshotBytes, bytes) 289 }) 290 } 291 292 // TestGetLatestProtocolStateSnapshot_PhaseTransitionSpan tests our GetLatestProtocolStateSnapshot RPC endpoint 293 // where the sealing segment for the state requested at latest finalized block contains a blocks that 294 // spans an epoch phase transition. 295 func (suite *Suite) TestGetLatestProtocolStateSnapshot_PhaseTransitionSpan() { 296 identities := unittest.CompleteIdentitySet() 297 rootSnapshot := unittest.RootSnapshotFixture(identities) 298 util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.MutableState) { 299 epochBuilder := unittest.NewEpochBuilder(suite.T(), state) 300 // build epoch 1 301 // blocks in current state 302 // P <- A(S_P-1) <- B(S_P) <- C(S_A) <- D(S_B) |setup| <- E(S_C) <- F(S_D) |commit| <- G(S_E) 303 epochBuilder. 304 BuildEpoch(). 305 CompleteEpoch() 306 307 // get heights of each phase in built epochs 308 epoch1, ok := epochBuilder.EpochHeights(1) 309 require.True(suite.T(), ok) 310 311 // setup AtBlockID mock returns for state 312 for _, height := range epoch1.Range() { 313 suite.state.On("AtHeight", height).Return(state.AtHeight(height)) 314 } 315 316 // Take snapshot at height of block E (epoch1.heights[4]) the sealing segment for this snapshot 317 // is C(S_A) <- D(S_B) |setup| <- E(S_C) which spans the epoch setup phase. This will force 318 // our RPC endpoint to return a snapshot at block D which is the snapshot at the boundary where the phase 319 // transition happens. 320 snap := state.AtHeight(epoch1.Range()[4]) 321 suite.state.On("Final").Return(snap).Once() 322 323 backend := New( 324 suite.state, 325 nil, 326 nil, 327 nil, 328 nil, 329 nil, 330 nil, 331 nil, 332 nil, 333 suite.chainID, 334 metrics.NewNoopCollector(), 335 nil, 336 false, 337 100, 338 nil, 339 nil, 340 suite.log, 341 DefaultSnapshotHistoryLimit, 342 ) 343 344 // query the handler for the latest finalized snapshot 345 bytes, err := backend.GetLatestProtocolStateSnapshot(context.Background()) 346 suite.Require().NoError(err) 347 348 // we expect the endpoint to return last valid snapshot which is the snapshot at block D (height 3) 349 expectedSnapshotBytes, err := convert.SnapshotToBytes(state.AtHeight(epoch1.Range()[3])) 350 suite.Require().NoError(err) 351 suite.Require().Equal(expectedSnapshotBytes, bytes) 352 }) 353 } 354 355 // TestGetLatestProtocolStateSnapshot_EpochTransitionSpan tests our GetLatestProtocolStateSnapshot RPC endpoint 356 // where the sealing segment for the state requested at latest finalized block contains a blocks that 357 // spans an epoch transition. 358 func (suite *Suite) TestGetLatestProtocolStateSnapshot_EpochTransitionSpan() { 359 identities := unittest.CompleteIdentitySet() 360 rootSnapshot := unittest.RootSnapshotFixture(identities) 361 util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.MutableState) { 362 epochBuilder := unittest.NewEpochBuilder(suite.T(), state) 363 // build epoch 1 364 // blocks in current state 365 // P <- A(S_P-1) <- B(S_P) <- C(S_A) <- D(S_B) |setup| <- E(S_C) <- F(S_D) |commit| <- G(S_E) 366 epochBuilder.BuildEpoch() 367 368 // add more blocks to our state in the commit phase, this will allow 369 // us to take a snapshot at the height where the epoch1 -> epoch2 transition 370 // and no block spans an epoch phase transition. The third block added will 371 // have a seal for the first block in the commit phase allowing us to avoid 372 // spanning an epoch phase transition. 373 epochBuilder.AddBlocksWithSeals(3, 1) 374 epochBuilder.CompleteEpoch() 375 376 // Now we build our second epoch 377 epochBuilder. 378 BuildEpoch(). 379 CompleteEpoch() 380 381 // get heights of each phase in built epochs 382 epoch1, ok := epochBuilder.EpochHeights(1) 383 require.True(suite.T(), ok) 384 epoch2, ok := epochBuilder.EpochHeights(2) 385 require.True(suite.T(), ok) 386 387 // setup AtHeight mock returns for state 388 for _, height := range append(epoch1.Range(), epoch2.Range()...) { 389 suite.state.On("AtHeight", height).Return(state.AtHeight(height)) 390 } 391 392 // Take snapshot at the first block of epoch2 . The sealing segment 393 // for this snapshot contains a block (highest) that spans the epoch1 -> epoch2 394 // transition. 395 snap := state.AtHeight(epoch2.Range()[0]) 396 suite.state.On("Final").Return(snap).Once() 397 398 backend := New( 399 suite.state, 400 nil, 401 nil, 402 nil, 403 nil, 404 nil, 405 nil, 406 nil, 407 nil, 408 suite.chainID, 409 metrics.NewNoopCollector(), 410 nil, 411 false, 412 100, 413 nil, 414 nil, 415 suite.log, 416 DefaultSnapshotHistoryLimit, 417 ) 418 419 // query the handler for the latest finalized snapshot 420 bytes, err := backend.GetLatestProtocolStateSnapshot(context.Background()) 421 suite.Require().NoError(err) 422 423 // we expect the endpoint to return last valid snapshot which is the snapshot at the final block 424 // of the previous epoch 425 expectedSnapshotBytes, err := convert.SnapshotToBytes(state.AtHeight(epoch1.Range()[len(epoch1.Range())-1])) 426 suite.Require().NoError(err) 427 suite.Require().Equal(expectedSnapshotBytes, bytes) 428 }) 429 } 430 431 // TestGetLatestProtocolStateSnapshot_EpochTransitionSpan tests our GetLatestProtocolStateSnapshot RPC endpoint 432 // where the length of the sealing segment is greater than the configured snapshotHistoryLimit 433 func (suite *Suite) TestGetLatestProtocolStateSnapshot_HistoryLimit() { 434 identities := unittest.CompleteIdentitySet() 435 rootSnapshot := unittest.RootSnapshotFixture(identities) 436 util.RunWithFullProtocolState(suite.T(), rootSnapshot, func(db *badger.DB, state *bprotocol.MutableState) { 437 epochBuilder := unittest.NewEpochBuilder(suite.T(), state).BuildEpoch().CompleteEpoch() 438 439 // get heights of each phase in built epochs 440 epoch1, ok := epochBuilder.EpochHeights(1) 441 require.True(suite.T(), ok) 442 443 // setup AtBlockID mock returns for state 444 for _, height := range epoch1.Range() { 445 suite.state.On("AtHeight", height).Return(state.AtHeight(height)) 446 } 447 448 // Take snapshot at height of block E (epoch1.heights[4]) the sealing segment for this snapshot 449 // is C(S_A) <- D(S_B) |setup| <- E(S_C) which spans the epoch setup phase. This will force 450 // our RPC endpoint to return a snapshot at block D which is the snapshot at the boundary where a phase 451 // transition happens. 452 snap := state.AtHeight(epoch1.Range()[4]) 453 suite.state.On("Final").Return(snap).Once() 454 455 // very short history limit, any segment with any blocks spanning any transition should force the endpoint to return a history limit error 456 snapshotHistoryLimit := 1 457 backend := New( 458 suite.state, 459 nil, 460 nil, 461 nil, 462 nil, 463 nil, 464 nil, 465 nil, 466 nil, 467 suite.chainID, 468 metrics.NewNoopCollector(), 469 nil, 470 false, 471 DefaultMaxHeightRange, 472 nil, 473 nil, 474 suite.log, 475 snapshotHistoryLimit, 476 ) 477 478 // the handler should return a snapshot history limit error 479 _, err := backend.GetLatestProtocolStateSnapshot(context.Background()) 480 suite.Require().ErrorIs(err, SnapshotHistoryLimitErr) 481 }) 482 } 483 484 func (suite *Suite) TestGetLatestSealedBlockHeader() { 485 // setup the mocks 486 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 487 488 block := unittest.BlockHeaderFixture() 489 suite.snapshot.On("Head").Return(block, nil).Once() 490 491 suite.state.On("Sealed").Return(suite.snapshot, nil) 492 suite.snapshot.On("Head").Return(block, nil).Once() 493 494 backend := New( 495 suite.state, 496 nil, 497 nil, 498 nil, 499 nil, 500 nil, 501 nil, 502 nil, 503 nil, 504 suite.chainID, 505 metrics.NewNoopCollector(), 506 nil, 507 false, 508 DefaultMaxHeightRange, 509 nil, 510 nil, 511 suite.log, 512 DefaultSnapshotHistoryLimit, 513 ) 514 515 // query the handler for the latest sealed block 516 header, status, err := backend.GetLatestBlockHeader(context.Background(), true) 517 suite.checkResponse(header, err) 518 519 // make sure we got the latest sealed block 520 suite.Require().Equal(block.ID(), header.ID()) 521 suite.Require().Equal(block.Height, header.Height) 522 suite.Require().Equal(block.ParentID, header.ParentID) 523 suite.Require().Equal(status, flow.BlockStatusSealed) 524 525 suite.assertAllExpectations() 526 } 527 528 func (suite *Suite) TestGetTransaction() { 529 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 530 531 transaction := unittest.TransactionFixture() 532 expected := transaction.TransactionBody 533 534 suite.transactions. 535 On("ByID", transaction.ID()). 536 Return(&expected, nil). 537 Once() 538 539 backend := New( 540 suite.state, 541 nil, 542 nil, 543 nil, 544 nil, 545 nil, 546 suite.transactions, 547 nil, 548 nil, 549 suite.chainID, 550 metrics.NewNoopCollector(), 551 nil, 552 false, 553 DefaultMaxHeightRange, 554 nil, 555 nil, 556 suite.log, 557 DefaultSnapshotHistoryLimit, 558 ) 559 560 actual, err := backend.GetTransaction(context.Background(), transaction.ID()) 561 suite.checkResponse(actual, err) 562 563 suite.Require().Equal(expected, *actual) 564 565 suite.assertAllExpectations() 566 } 567 568 func (suite *Suite) TestGetCollection() { 569 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 570 571 expected := unittest.CollectionFixture(1).Light() 572 573 suite.collections. 574 On("LightByID", expected.ID()). 575 Return(&expected, nil). 576 Once() 577 578 backend := New( 579 suite.state, 580 nil, 581 nil, 582 nil, 583 nil, 584 suite.collections, 585 suite.transactions, 586 nil, 587 nil, 588 suite.chainID, 589 metrics.NewNoopCollector(), 590 nil, 591 false, 592 DefaultMaxHeightRange, 593 nil, 594 nil, 595 suite.log, 596 DefaultSnapshotHistoryLimit, 597 ) 598 599 actual, err := backend.GetCollectionByID(context.Background(), expected.ID()) 600 suite.transactions.AssertExpectations(suite.T()) 601 suite.checkResponse(actual, err) 602 603 suite.Equal(expected, *actual) 604 suite.assertAllExpectations() 605 } 606 607 // TestGetTransactionResultByIndex tests that the request is forwarded to EN 608 func (suite *Suite) TestGetTransactionResultByIndex() { 609 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 610 611 ctx := context.Background() 612 block := unittest.BlockFixture() 613 blockId := block.ID() 614 index := uint32(0) 615 616 suite.snapshot.On("Head").Return(block.Header, nil) 617 618 // block storage returns the corresponding block 619 suite.blocks. 620 On("ByID", blockId). 621 Return(&block, nil) 622 623 _, fixedENIDs := suite.setupReceipts(&block) 624 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 625 suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil) 626 627 // create a mock connection factory 628 connFactory := new(backendmock.ConnectionFactory) 629 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 630 631 exeEventReq := &execproto.GetTransactionByIndexRequest{ 632 BlockId: blockId[:], 633 Index: index, 634 } 635 636 exeEventResp := &execproto.GetTransactionResultResponse{ 637 Events: nil, 638 } 639 640 backend := New( 641 suite.state, 642 nil, 643 nil, 644 suite.blocks, 645 suite.headers, 646 suite.collections, 647 suite.transactions, 648 suite.receipts, 649 suite.results, 650 suite.chainID, 651 metrics.NewNoopCollector(), 652 connFactory, // the connection factory should be used to get the execution node client 653 false, 654 DefaultMaxHeightRange, 655 nil, 656 flow.IdentifierList(fixedENIDs.NodeIDs()).Strings(), 657 suite.log, 658 DefaultSnapshotHistoryLimit, 659 ) 660 suite.execClient. 661 On("GetTransactionResultByIndex", ctx, exeEventReq). 662 Return(exeEventResp, nil). 663 Once() 664 665 result, err := backend.GetTransactionResultByIndex(ctx, blockId, index) 666 suite.checkResponse(result, err) 667 suite.Assert().Equal(result.BlockHeight, block.Header.Height) 668 669 suite.assertAllExpectations() 670 } 671 672 func (suite *Suite) TestGetTransactionResultsByBlockID() { 673 head := unittest.BlockHeaderFixture() 674 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 675 suite.snapshot.On("Head").Return(head, nil) 676 677 ctx := context.Background() 678 block := unittest.BlockFixture() 679 blockId := block.ID() 680 681 // block storage returns the corresponding block 682 suite.blocks. 683 On("ByID", blockId). 684 Return(&block, nil) 685 686 _, fixedENIDs := suite.setupReceipts(&block) 687 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 688 suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil) 689 690 // create a mock connection factory 691 connFactory := new(backendmock.ConnectionFactory) 692 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 693 694 exeEventReq := &execproto.GetTransactionsByBlockIDRequest{ 695 BlockId: blockId[:], 696 } 697 698 exeEventResp := &execproto.GetTransactionResultsResponse{ 699 TransactionResults: []*execproto.GetTransactionResultResponse{{}}, 700 } 701 702 backend := New( 703 suite.state, 704 nil, 705 nil, 706 suite.blocks, 707 suite.headers, 708 suite.collections, 709 suite.transactions, 710 suite.receipts, 711 suite.results, 712 suite.chainID, 713 metrics.NewNoopCollector(), 714 connFactory, // the connection factory should be used to get the execution node client 715 false, 716 DefaultMaxHeightRange, 717 nil, 718 flow.IdentifierList(fixedENIDs.NodeIDs()).Strings(), 719 suite.log, 720 DefaultSnapshotHistoryLimit, 721 ) 722 suite.execClient. 723 On("GetTransactionResultsByBlockID", ctx, exeEventReq). 724 Return(exeEventResp, nil). 725 Once() 726 727 result, err := backend.GetTransactionResultsByBlockID(ctx, blockId) 728 suite.checkResponse(result, err) 729 730 suite.assertAllExpectations() 731 } 732 733 // TestTransactionStatusTransition tests that the status of transaction changes from Finalized to Sealed 734 // when the protocol state is updated 735 func (suite *Suite) TestTransactionStatusTransition() { 736 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 737 738 ctx := context.Background() 739 collection := unittest.CollectionFixture(1) 740 transactionBody := collection.Transactions[0] 741 block := unittest.BlockFixture() 742 block.Header.Height = 2 743 headBlock := unittest.BlockFixture() 744 headBlock.Header.Height = block.Header.Height - 1 // head is behind the current block 745 746 suite.snapshot. 747 On("Head"). 748 Return(headBlock.Header, nil) 749 750 light := collection.Light() 751 752 // transaction storage returns the corresponding transaction 753 suite.transactions. 754 On("ByID", transactionBody.ID()). 755 Return(transactionBody, nil) 756 757 // collection storage returns the corresponding collection 758 suite.collections. 759 On("LightByTransactionID", transactionBody.ID()). 760 Return(&light, nil) 761 762 // block storage returns the corresponding block 763 suite.blocks. 764 On("ByCollectionID", collection.ID()). 765 Return(&block, nil) 766 767 txID := transactionBody.ID() 768 blockID := block.ID() 769 _, fixedENIDs := suite.setupReceipts(&block) 770 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 771 suite.snapshot.On("Identities", mock.Anything).Return(fixedENIDs, nil) 772 773 // create a mock connection factory 774 connFactory := new(backendmock.ConnectionFactory) 775 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 776 connFactory.On("InvalidateExecutionAPIClient", mock.Anything) 777 778 exeEventReq := &execproto.GetTransactionResultRequest{ 779 BlockId: blockID[:], 780 TransactionId: txID[:], 781 } 782 783 exeEventResp := &execproto.GetTransactionResultResponse{ 784 Events: nil, 785 } 786 787 backend := New( 788 suite.state, 789 nil, 790 nil, 791 suite.blocks, 792 suite.headers, 793 suite.collections, 794 suite.transactions, 795 suite.receipts, 796 suite.results, 797 suite.chainID, 798 metrics.NewNoopCollector(), 799 connFactory, // the connection factory should be used to get the execution node client 800 false, 801 DefaultMaxHeightRange, 802 nil, 803 flow.IdentifierList(fixedENIDs.NodeIDs()).Strings(), 804 suite.log, 805 DefaultSnapshotHistoryLimit, 806 ) 807 808 // Successfully return empty event list 809 suite.execClient. 810 On("GetTransactionResult", ctx, exeEventReq). 811 Return(exeEventResp, status.Errorf(codes.NotFound, "not found")). 812 Once() 813 814 // first call - when block under test is greater height than the sealed head, but execution node does not know about Tx 815 result, err := backend.GetTransactionResult(ctx, txID) 816 suite.checkResponse(result, err) 817 818 // status should be finalized since the sealed blocks is smaller in height 819 suite.Assert().Equal(flow.TransactionStatusFinalized, result.Status) 820 821 // block ID should be included in the response 822 suite.Assert().Equal(blockID, result.BlockID) 823 824 // Successfully return empty event list from here on 825 suite.execClient. 826 On("GetTransactionResult", ctx, exeEventReq). 827 Return(exeEventResp, nil) 828 829 // second call - when block under test's height is greater height than the sealed head 830 result, err = backend.GetTransactionResult(ctx, txID) 831 suite.checkResponse(result, err) 832 833 // status should be executed since no `NotFound` error in the `GetTransactionResult` call 834 suite.Assert().Equal(flow.TransactionStatusExecuted, result.Status) 835 836 // now let the head block be finalized 837 headBlock.Header.Height = block.Header.Height + 1 838 839 // third call - when block under test's height is less than sealed head's height 840 result, err = backend.GetTransactionResult(ctx, txID) 841 suite.checkResponse(result, err) 842 843 // status should be sealed since the sealed blocks is greater in height 844 suite.Assert().Equal(flow.TransactionStatusSealed, result.Status) 845 846 // now go far into the future 847 headBlock.Header.Height = block.Header.Height + flow.DefaultTransactionExpiry + 1 848 849 // fourth call - when block under test's height so much less than the head's height that it's considered expired, 850 // but since there is a execution result, means it should retain it's sealed status 851 result, err = backend.GetTransactionResult(ctx, txID) 852 suite.checkResponse(result, err) 853 854 // status should be expired since 855 suite.Assert().Equal(flow.TransactionStatusSealed, result.Status) 856 857 suite.assertAllExpectations() 858 } 859 860 // TestTransactionExpiredStatusTransition tests that the status 861 // of transaction changes from Pending to Expired when enough blocks pass 862 func (suite *Suite) TestTransactionExpiredStatusTransition() { 863 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 864 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 865 866 ctx := context.Background() 867 collection := unittest.CollectionFixture(1) 868 transactionBody := collection.Transactions[0] 869 block := unittest.BlockFixture() 870 block.Header.Height = 2 871 transactionBody.SetReferenceBlockID(block.ID()) 872 873 headBlock := unittest.BlockFixture() 874 headBlock.Header.Height = block.Header.Height - 1 // head is behind the current block 875 876 // set up GetLastFullBlockHeight mock 877 fullHeight := headBlock.Header.Height 878 suite.blocks.On("GetLastFullBlockHeight").Return( 879 func() uint64 { return fullHeight }, 880 func() error { return nil }, 881 ) 882 883 suite.snapshot. 884 On("Head"). 885 Return(headBlock.Header, nil) 886 887 snapshotAtBlock := new(protocol.Snapshot) 888 snapshotAtBlock.On("Head").Return(block.Header, nil) 889 890 suite.state. 891 On("AtBlockID", block.ID()). 892 Return(snapshotAtBlock, nil) 893 894 // transaction storage returns the corresponding transaction 895 suite.transactions. 896 On("ByID", transactionBody.ID()). 897 Return(transactionBody, nil) 898 899 // collection storage returns a not found error 900 suite.collections. 901 On("LightByTransactionID", transactionBody.ID()). 902 Return(nil, storage.ErrNotFound) 903 904 txID := transactionBody.ID() 905 906 backend := New( 907 suite.state, 908 nil, 909 nil, 910 suite.blocks, 911 suite.headers, 912 suite.collections, 913 suite.transactions, 914 nil, 915 nil, 916 suite.chainID, 917 metrics.NewNoopCollector(), 918 nil, 919 false, 920 DefaultMaxHeightRange, 921 nil, 922 nil, 923 suite.log, 924 DefaultSnapshotHistoryLimit, 925 ) 926 927 // should return pending status when we have not observed an expiry block 928 suite.Run("pending", func() { 929 // referenced block isn't known yet, so should return pending status 930 result, err := backend.GetTransactionResult(ctx, txID) 931 suite.checkResponse(result, err) 932 933 suite.Assert().Equal(flow.TransactionStatusPending, result.Status) 934 }) 935 936 // should return pending status when we have observed an expiry block but 937 // have not observed all intermediary collections 938 suite.Run("expiry un-confirmed", func() { 939 940 suite.Run("ONLY finalized expiry block", func() { 941 // we have finalized an expiry block 942 headBlock.Header.Height = block.Header.Height + flow.DefaultTransactionExpiry + 1 943 // we have NOT observed all intermediary collections 944 fullHeight = block.Header.Height + flow.DefaultTransactionExpiry/2 945 946 result, err := backend.GetTransactionResult(ctx, txID) 947 suite.checkResponse(result, err) 948 suite.Assert().Equal(flow.TransactionStatusPending, result.Status) 949 }) 950 suite.Run("ONLY observed intermediary collections", func() { 951 // we have NOT finalized an expiry block 952 headBlock.Header.Height = block.Header.Height + flow.DefaultTransactionExpiry/2 953 // we have observed all intermediary collections 954 fullHeight = block.Header.Height + flow.DefaultTransactionExpiry + 1 955 956 result, err := backend.GetTransactionResult(ctx, txID) 957 suite.checkResponse(result, err) 958 suite.Assert().Equal(flow.TransactionStatusPending, result.Status) 959 }) 960 961 }) 962 963 // should return expired status only when we have observed an expiry block 964 // and have observed all intermediary collections 965 suite.Run("expired", func() { 966 // we have finalized an expiry block 967 headBlock.Header.Height = block.Header.Height + flow.DefaultTransactionExpiry + 1 968 // we have observed all intermediary collections 969 fullHeight = block.Header.Height + flow.DefaultTransactionExpiry + 1 970 971 result, err := backend.GetTransactionResult(ctx, txID) 972 suite.checkResponse(result, err) 973 suite.Assert().Equal(flow.TransactionStatusExpired, result.Status) 974 }) 975 976 suite.assertAllExpectations() 977 } 978 979 // TestTransactionPendingToFinalizedStatusTransition tests that the status of transaction changes from Finalized to Expired 980 func (suite *Suite) TestTransactionPendingToFinalizedStatusTransition() { 981 982 ctx := context.Background() 983 collection := unittest.CollectionFixture(1) 984 transactionBody := collection.Transactions[0] 985 // block which will eventually contain the transaction 986 block := unittest.BlockFixture() 987 blockID := block.ID() 988 // reference block to which the transaction points to 989 refBlock := unittest.BlockFixture() 990 refBlockID := refBlock.ID() 991 refBlock.Header.Height = 2 992 transactionBody.SetReferenceBlockID(refBlockID) 993 txID := transactionBody.ID() 994 995 headBlock := unittest.BlockFixture() 996 headBlock.Header.Height = refBlock.Header.Height - 1 // head is behind the current refBlock 997 998 suite.snapshot. 999 On("Head"). 1000 Return(headBlock.Header, nil) 1001 1002 snapshotAtBlock := new(protocol.Snapshot) 1003 snapshotAtBlock.On("Head").Return(refBlock.Header, nil) 1004 1005 _, enIDs := suite.setupReceipts(&block) 1006 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 1007 1008 suite.snapshot.On("Identities", mock.Anything).Return(enIDs, nil) 1009 1010 suite.state. 1011 On("AtBlockID", refBlockID). 1012 Return(snapshotAtBlock, nil) 1013 1014 suite.state.On("Final").Return(snapshotAtBlock, nil).Maybe() 1015 1016 // transaction storage returns the corresponding transaction 1017 suite.transactions. 1018 On("ByID", txID). 1019 Return(transactionBody, nil) 1020 1021 currentState := flow.TransactionStatusPending // marker for the current state 1022 // collection storage returns a not found error if tx is pending, else it returns the collection light reference 1023 suite.collections. 1024 On("LightByTransactionID", txID). 1025 Return(func(txID flow.Identifier) *flow.LightCollection { 1026 if currentState == flow.TransactionStatusPending { 1027 return nil 1028 } 1029 collLight := collection.Light() 1030 return &collLight 1031 }, 1032 func(txID flow.Identifier) error { 1033 if currentState == flow.TransactionStatusPending { 1034 return storage.ErrNotFound 1035 } 1036 return nil 1037 }) 1038 1039 // refBlock storage returns the corresponding refBlock 1040 suite.blocks. 1041 On("ByCollectionID", collection.ID()). 1042 Return(&block, nil) 1043 1044 receipts, _ := suite.setupReceipts(&block) 1045 1046 exeEventReq := &execproto.GetTransactionResultRequest{ 1047 BlockId: blockID[:], 1048 TransactionId: txID[:], 1049 } 1050 1051 exeEventResp := &execproto.GetTransactionResultResponse{ 1052 Events: nil, 1053 } 1054 1055 // simulate that the execution node has not yet executed the transaction 1056 suite.execClient. 1057 On("GetTransactionResult", ctx, exeEventReq). 1058 Return(exeEventResp, status.Errorf(codes.NotFound, "not found")). 1059 Once() 1060 1061 // create a mock connection factory 1062 connFactory := suite.setupConnectionFactory() 1063 1064 backend := New( 1065 suite.state, 1066 nil, 1067 nil, 1068 suite.blocks, 1069 suite.headers, 1070 suite.collections, 1071 suite.transactions, 1072 suite.receipts, 1073 suite.results, 1074 suite.chainID, 1075 metrics.NewNoopCollector(), 1076 connFactory, // the connection factory should be used to get the execution node client 1077 false, 1078 100, 1079 nil, 1080 flow.IdentifierList(enIDs.NodeIDs()).Strings(), 1081 suite.log, 1082 DefaultSnapshotHistoryLimit, 1083 ) 1084 1085 preferredENIdentifiers = flow.IdentifierList{receipts[0].ExecutorID} 1086 1087 // should return pending status when we have not observed collection for the transaction 1088 suite.Run("pending", func() { 1089 currentState = flow.TransactionStatusPending 1090 result, err := backend.GetTransactionResult(ctx, txID) 1091 suite.checkResponse(result, err) 1092 suite.Assert().Equal(flow.TransactionStatusPending, result.Status) 1093 // assert that no call to an execution node is made 1094 suite.execClient.AssertNotCalled(suite.T(), "GetTransactionResult", mock.Anything, mock.Anything) 1095 }) 1096 1097 // should return finalized status when we have have observed collection for the transaction (after observing the 1098 // a preceding sealed refBlock) 1099 suite.Run("finalized", func() { 1100 currentState = flow.TransactionStatusFinalized 1101 result, err := backend.GetTransactionResult(ctx, txID) 1102 suite.checkResponse(result, err) 1103 suite.Assert().Equal(flow.TransactionStatusFinalized, result.Status) 1104 }) 1105 1106 suite.assertAllExpectations() 1107 } 1108 1109 // TestTransactionResultUnknown tests that the status of transaction is reported as unknown when it is not found in the 1110 // local storage 1111 func (suite *Suite) TestTransactionResultUnknown() { 1112 1113 ctx := context.Background() 1114 txID := unittest.IdentifierFixture() 1115 1116 // transaction storage returns an error 1117 suite.transactions. 1118 On("ByID", txID). 1119 Return(nil, storage.ErrNotFound) 1120 1121 backend := New( 1122 suite.state, 1123 nil, 1124 nil, 1125 nil, 1126 nil, 1127 nil, 1128 suite.transactions, 1129 nil, 1130 nil, 1131 suite.chainID, 1132 metrics.NewNoopCollector(), 1133 nil, 1134 false, 1135 DefaultMaxHeightRange, 1136 nil, 1137 nil, 1138 suite.log, 1139 DefaultSnapshotHistoryLimit, 1140 ) 1141 1142 // first call - when block under test is greater height than the sealed head, but execution node does not know about Tx 1143 result, err := backend.GetTransactionResult(ctx, txID) 1144 suite.checkResponse(result, err) 1145 1146 // status should be reported as unknown 1147 suite.Assert().Equal(flow.TransactionStatusUnknown, result.Status) 1148 } 1149 1150 func (suite *Suite) TestGetLatestFinalizedBlock() { 1151 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1152 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 1153 1154 // setup the mocks 1155 expected := unittest.BlockFixture() 1156 header := expected.Header 1157 1158 suite.snapshot. 1159 On("Head"). 1160 Return(header, nil).Once() 1161 1162 headerClone := *header 1163 headerClone.Height = 0 1164 1165 suite.snapshot. 1166 On("Head"). 1167 Return(&headerClone, nil). 1168 Once() 1169 1170 suite.blocks. 1171 On("ByID", header.ID()). 1172 Return(&expected, nil) 1173 1174 backend := New( 1175 suite.state, 1176 nil, 1177 nil, 1178 suite.blocks, 1179 nil, 1180 nil, 1181 nil, 1182 nil, 1183 nil, 1184 suite.chainID, 1185 metrics.NewNoopCollector(), 1186 nil, 1187 false, 1188 DefaultMaxHeightRange, 1189 nil, 1190 nil, 1191 suite.log, 1192 DefaultSnapshotHistoryLimit, 1193 ) 1194 1195 // query the handler for the latest finalized header 1196 actual, status, err := backend.GetLatestBlock(context.Background(), false) 1197 suite.checkResponse(actual, err) 1198 1199 // make sure we got the latest header 1200 suite.Require().Equal(expected, *actual) 1201 suite.Assert().Equal(status, flow.BlockStatusFinalized) 1202 1203 suite.assertAllExpectations() 1204 } 1205 1206 type mockCloser struct{} 1207 1208 func (mc *mockCloser) Close() error { return nil } 1209 1210 func (suite *Suite) TestGetEventsForBlockIDs() { 1211 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1212 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 1213 1214 events := getEvents(10) 1215 validExecutorIdentities := flow.IdentityList{} 1216 1217 setupStorage := func(n int) []*flow.Header { 1218 headers := make([]*flow.Header, n) 1219 ids := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 1220 1221 for i := 0; i < n; i++ { 1222 b := unittest.BlockFixture() 1223 suite.headers. 1224 On("ByBlockID", b.ID()). 1225 Return(b.Header, nil).Once() 1226 1227 headers[i] = b.Header 1228 1229 receipt1 := unittest.ReceiptForBlockFixture(&b) 1230 receipt1.ExecutorID = ids[0].NodeID 1231 receipt2 := unittest.ReceiptForBlockFixture(&b) 1232 receipt2.ExecutorID = ids[1].NodeID 1233 receipt1.ExecutionResult = receipt2.ExecutionResult 1234 suite.receipts. 1235 On("ByBlockID", b.ID()). 1236 Return(flow.ExecutionReceiptList{receipt1, receipt2}, nil).Once() 1237 validExecutorIdentities = append(validExecutorIdentities, ids...) 1238 } 1239 1240 return headers 1241 } 1242 blockHeaders := setupStorage(5) 1243 1244 suite.snapshot.On("Identities", mock.Anything).Return(validExecutorIdentities, nil) 1245 validENIDs := flow.IdentifierList(validExecutorIdentities.NodeIDs()) 1246 1247 // create a mock connection factory 1248 connFactory := new(backendmock.ConnectionFactory) 1249 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 1250 1251 // create the expected results from execution node and access node 1252 exeResults := make([]*execproto.GetEventsForBlockIDsResponse_Result, len(blockHeaders)) 1253 1254 for i := 0; i < len(blockHeaders); i++ { 1255 exeResults[i] = &execproto.GetEventsForBlockIDsResponse_Result{ 1256 BlockId: convert.IdentifierToMessage(blockHeaders[i].ID()), 1257 BlockHeight: blockHeaders[i].Height, 1258 Events: convert.EventsToMessages(events), 1259 } 1260 } 1261 1262 expected := make([]flow.BlockEvents, len(blockHeaders)) 1263 for i := 0; i < len(blockHeaders); i++ { 1264 expected[i] = flow.BlockEvents{ 1265 BlockID: blockHeaders[i].ID(), 1266 BlockHeight: blockHeaders[i].Height, 1267 BlockTimestamp: blockHeaders[i].Timestamp, 1268 Events: events, 1269 } 1270 } 1271 1272 // create the execution node response 1273 exeResp := &execproto.GetEventsForBlockIDsResponse{ 1274 Results: exeResults, 1275 } 1276 1277 ctx := context.Background() 1278 1279 blockIDs := make([]flow.Identifier, len(blockHeaders)) 1280 for i, header := range blockHeaders { 1281 blockIDs[i] = header.ID() 1282 } 1283 exeReq := &execproto.GetEventsForBlockIDsRequest{ 1284 BlockIds: convert.IdentifiersToMessages(blockIDs), 1285 Type: string(flow.EventAccountCreated), 1286 } 1287 1288 // create receipt mocks that always returns empty 1289 receipts := new(storagemock.ExecutionReceipts) 1290 receipts. 1291 On("ByBlockID", mock.Anything). 1292 Return(flow.ExecutionReceiptList{}, nil) 1293 1294 // expect two calls to the executor api client (one for each of the following 2 test cases) 1295 suite.execClient. 1296 On("GetEventsForBlockIDs", ctx, exeReq). 1297 Return(exeResp, nil). 1298 Once() 1299 1300 suite.Run("with an execution node chosen using block ID form the list of Fixed ENs", func() { 1301 1302 // create the handler 1303 backend := New( 1304 suite.state, 1305 nil, 1306 nil, 1307 nil, 1308 suite.headers, 1309 nil, 1310 nil, 1311 suite.receipts, 1312 suite.results, 1313 suite.chainID, 1314 metrics.NewNoopCollector(), 1315 connFactory, // the connection factory should be used to get the execution node client 1316 false, 1317 DefaultMaxHeightRange, 1318 nil, 1319 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1320 suite.log, 1321 DefaultSnapshotHistoryLimit, 1322 ) 1323 1324 // execute request 1325 actual, err := backend.GetEventsForBlockIDs(ctx, string(flow.EventAccountCreated), blockIDs) 1326 suite.checkResponse(actual, err) 1327 1328 suite.Require().Equal(expected, actual) 1329 }) 1330 1331 suite.Run("with an empty block ID list", func() { 1332 1333 // create the handler 1334 backend := New( 1335 suite.state, 1336 nil, 1337 nil, 1338 nil, 1339 suite.headers, 1340 nil, 1341 nil, 1342 receipts, 1343 nil, 1344 suite.chainID, 1345 metrics.NewNoopCollector(), 1346 connFactory, // the connection factory should be used to get the execution node client 1347 false, 1348 DefaultMaxHeightRange, 1349 nil, 1350 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1351 suite.log, 1352 DefaultSnapshotHistoryLimit, 1353 ) 1354 1355 // execute request with an empty block id list and expect an empty list of events and no error 1356 resp, err := backend.GetEventsForBlockIDs(ctx, string(flow.EventAccountCreated), []flow.Identifier{}) 1357 require.NoError(suite.T(), err) 1358 require.Empty(suite.T(), resp) 1359 }) 1360 1361 suite.assertAllExpectations() 1362 } 1363 1364 func (suite *Suite) TestGetExecutionResultByID() { 1365 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1366 1367 validExecutorIdentities := flow.IdentityList{} 1368 validENIDs := flow.IdentifierList(validExecutorIdentities.NodeIDs()) 1369 1370 // create a mock connection factory 1371 connFactory := new(backendmock.ConnectionFactory) 1372 1373 nonexistingID := unittest.IdentifierFixture() 1374 blockID := unittest.IdentifierFixture() 1375 executionResult := unittest.ExecutionResultFixture( 1376 unittest.WithExecutionResultBlockID(blockID)) 1377 1378 ctx := context.Background() 1379 1380 results := new(storagemock.ExecutionResults) 1381 results. 1382 On("ByID", nonexistingID). 1383 Return(nil, storage.ErrNotFound) 1384 1385 results. 1386 On("ByID", executionResult.ID()). 1387 Return(executionResult, nil) 1388 1389 suite.Run("nonexisting execution result for id", func() { 1390 1391 // create the handler 1392 backend := New( 1393 suite.state, 1394 nil, 1395 nil, 1396 nil, 1397 suite.headers, 1398 nil, 1399 nil, 1400 suite.receipts, 1401 results, 1402 suite.chainID, 1403 metrics.NewNoopCollector(), 1404 connFactory, // the connection factory should be used to get the execution node client 1405 false, 1406 DefaultMaxHeightRange, 1407 nil, 1408 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1409 suite.log, 1410 DefaultSnapshotHistoryLimit, 1411 ) 1412 1413 // execute request 1414 _, err := backend.GetExecutionResultByID(ctx, nonexistingID) 1415 1416 assert.Error(suite.T(), err) 1417 }) 1418 1419 suite.Run("existing execution result id", func() { 1420 // create the handler 1421 backend := New( 1422 suite.state, 1423 nil, 1424 nil, 1425 nil, 1426 suite.headers, 1427 nil, 1428 nil, 1429 nil, 1430 results, 1431 suite.chainID, 1432 metrics.NewNoopCollector(), 1433 connFactory, // the connection factory should be used to get the execution node client 1434 false, 1435 DefaultMaxHeightRange, 1436 nil, 1437 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1438 suite.log, 1439 DefaultSnapshotHistoryLimit, 1440 ) 1441 1442 // execute request 1443 er, err := backend.GetExecutionResultByID(ctx, executionResult.ID()) 1444 suite.checkResponse(er, err) 1445 1446 require.Equal(suite.T(), executionResult, er) 1447 }) 1448 1449 results.AssertExpectations(suite.T()) 1450 suite.assertAllExpectations() 1451 } 1452 1453 func (suite *Suite) TestGetExecutionResultByBlockID() { 1454 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1455 1456 validExecutorIdentities := flow.IdentityList{} 1457 validENIDs := flow.IdentifierList(validExecutorIdentities.NodeIDs()) 1458 1459 // create a mock connection factory 1460 connFactory := new(backendmock.ConnectionFactory) 1461 1462 blockID := unittest.IdentifierFixture() 1463 executionResult := unittest.ExecutionResultFixture( 1464 unittest.WithExecutionResultBlockID(blockID), 1465 unittest.WithServiceEvents(2)) 1466 1467 ctx := context.Background() 1468 1469 nonexistingBlockID := unittest.IdentifierFixture() 1470 1471 results := new(storagemock.ExecutionResults) 1472 results. 1473 On("ByBlockID", nonexistingBlockID). 1474 Return(nil, storage.ErrNotFound) 1475 1476 results. 1477 On("ByBlockID", blockID). 1478 Return(executionResult, nil) 1479 1480 suite.Run("nonexisting execution results", func() { 1481 1482 // create the handler 1483 backend := New( 1484 suite.state, 1485 nil, 1486 nil, 1487 nil, 1488 suite.headers, 1489 nil, 1490 nil, 1491 suite.receipts, 1492 results, 1493 suite.chainID, 1494 metrics.NewNoopCollector(), 1495 connFactory, // the connection factory should be used to get the execution node client 1496 false, 1497 DefaultMaxHeightRange, 1498 nil, 1499 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1500 suite.log, 1501 DefaultSnapshotHistoryLimit, 1502 ) 1503 1504 // execute request 1505 _, err := backend.GetExecutionResultForBlockID(ctx, nonexistingBlockID) 1506 1507 assert.Error(suite.T(), err) 1508 }) 1509 1510 suite.Run("existing execution results", func() { 1511 1512 // create the handler 1513 backend := New( 1514 suite.state, 1515 nil, 1516 nil, 1517 nil, 1518 suite.headers, 1519 nil, 1520 nil, 1521 nil, 1522 results, 1523 suite.chainID, 1524 metrics.NewNoopCollector(), 1525 connFactory, // the connection factory should be used to get the execution node client 1526 false, 1527 DefaultMaxHeightRange, 1528 nil, 1529 validENIDs.Strings(), // set the fixed EN Identifiers to the generated execution IDs 1530 suite.log, 1531 DefaultSnapshotHistoryLimit, 1532 ) 1533 1534 // execute request 1535 er, err := backend.GetExecutionResultForBlockID(ctx, blockID) 1536 suite.checkResponse(er, err) 1537 1538 require.Equal(suite.T(), executionResult, er) 1539 }) 1540 1541 results.AssertExpectations(suite.T()) 1542 suite.assertAllExpectations() 1543 } 1544 1545 func (suite *Suite) TestGetEventsForHeightRange() { 1546 1547 ctx := context.Background() 1548 const minHeight uint64 = 5 1549 const maxHeight uint64 = 10 1550 var headHeight uint64 1551 var blockHeaders []*flow.Header 1552 var nodeIdentities flow.IdentityList 1553 1554 headersDB := make(map[uint64]*flow.Header) // backend for storage.Headers 1555 var head *flow.Header // backend for Snapshot.Head 1556 1557 state := new(protocol.State) 1558 snapshot := new(protocol.Snapshot) 1559 state.On("Final").Return(snapshot, nil) 1560 state.On("Sealed").Return(snapshot, nil) 1561 1562 rootHeader := unittest.BlockHeaderFixture() 1563 params := new(protocol.Params) 1564 params.On("Root").Return(rootHeader, nil) 1565 state.On("Params").Return(params).Maybe() 1566 1567 // mock snapshot to return head backend 1568 snapshot.On("Head").Return( 1569 func() *flow.Header { return head }, 1570 func() error { return nil }, 1571 ) 1572 snapshot.On("Identities", mock.Anything).Return( 1573 func(_ flow.IdentityFilter) flow.IdentityList { 1574 return nodeIdentities 1575 }, 1576 func(flow.IdentityFilter) error { return nil }, 1577 ) 1578 1579 // mock headers to pull from headers backend 1580 suite.headers.On("ByHeight", mock.Anything).Return( 1581 func(height uint64) *flow.Header { 1582 return headersDB[height] 1583 }, 1584 func(height uint64) error { 1585 _, ok := headersDB[height] 1586 if !ok { 1587 return storage.ErrNotFound 1588 } 1589 return nil 1590 }).Maybe() 1591 1592 setupHeadHeight := func(height uint64) { 1593 header := unittest.BlockHeaderFixture() // create a mock header 1594 header.Height = height // set the header height 1595 head = header 1596 } 1597 1598 setupStorage := func(min uint64, max uint64) ([]*flow.Header, []*flow.ExecutionReceipt, flow.IdentityList) { 1599 headersDB = make(map[uint64]*flow.Header) // reset backend 1600 1601 var headers []*flow.Header 1602 var ers []*flow.ExecutionReceipt 1603 var enIDs flow.IdentityList 1604 for i := min; i <= max; i++ { 1605 block := unittest.BlockFixture() 1606 header := block.Header 1607 headersDB[i] = header 1608 headers = append(headers, header) 1609 newErs, ids := suite.setupReceipts(&block) 1610 ers = append(ers, newErs...) 1611 enIDs = append(enIDs, ids...) 1612 } 1613 return headers, ers, enIDs 1614 } 1615 1616 setupExecClient := func() []flow.BlockEvents { 1617 blockIDs := make([]flow.Identifier, len(blockHeaders)) 1618 for i, header := range blockHeaders { 1619 blockIDs[i] = header.ID() 1620 } 1621 execReq := &execproto.GetEventsForBlockIDsRequest{ 1622 BlockIds: convert.IdentifiersToMessages(blockIDs), 1623 Type: string(flow.EventAccountCreated), 1624 } 1625 1626 results := make([]flow.BlockEvents, len(blockHeaders)) 1627 exeResults := make([]*execproto.GetEventsForBlockIDsResponse_Result, len(blockHeaders)) 1628 1629 for i, header := range blockHeaders { 1630 events := getEvents(1) 1631 height := header.Height 1632 1633 results[i] = flow.BlockEvents{ 1634 BlockID: header.ID(), 1635 BlockHeight: height, 1636 BlockTimestamp: header.Timestamp, 1637 Events: events, 1638 } 1639 1640 exeResults[i] = &execproto.GetEventsForBlockIDsResponse_Result{ 1641 BlockId: convert.IdentifierToMessage(header.ID()), 1642 BlockHeight: header.Height, 1643 Events: convert.EventsToMessages(events), 1644 } 1645 } 1646 1647 exeResp := &execproto.GetEventsForBlockIDsResponse{ 1648 Results: exeResults, 1649 } 1650 1651 suite.execClient. 1652 On("GetEventsForBlockIDs", ctx, execReq). 1653 Return(exeResp, nil). 1654 Once() 1655 1656 return results 1657 } 1658 1659 connFactory := suite.setupConnectionFactory() 1660 1661 suite.Run("invalid request max height < min height", func() { 1662 backend := New( 1663 suite.state, 1664 nil, 1665 nil, 1666 nil, 1667 suite.headers, 1668 nil, 1669 nil, 1670 suite.receipts, 1671 suite.results, 1672 suite.chainID, 1673 metrics.NewNoopCollector(), 1674 connFactory, // the connection factory should be used to get the execution node client 1675 false, 1676 DefaultMaxHeightRange, 1677 nil, 1678 nil, 1679 suite.log, 1680 DefaultSnapshotHistoryLimit, 1681 ) 1682 1683 _, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), maxHeight, minHeight) 1684 suite.Require().Error(err) 1685 1686 suite.assertAllExpectations() // assert that request was not sent to execution node 1687 }) 1688 1689 suite.Run("valid request with min_height < max_height < last_sealed_block_height", func() { 1690 1691 headHeight = maxHeight + 1 1692 1693 // setup mocks 1694 setupHeadHeight(headHeight) 1695 blockHeaders, _, nodeIdentities = setupStorage(minHeight, maxHeight) 1696 expectedResp := setupExecClient() 1697 fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() 1698 1699 // create handler 1700 backend := New( 1701 state, 1702 nil, 1703 nil, 1704 suite.blocks, 1705 suite.headers, 1706 nil, 1707 nil, 1708 suite.receipts, 1709 suite.results, 1710 suite.chainID, 1711 metrics.NewNoopCollector(), 1712 connFactory, // the connection factory should be used to get the execution node client 1713 false, 1714 DefaultMaxHeightRange, 1715 nil, 1716 fixedENIdentifiersStr, 1717 suite.log, 1718 DefaultSnapshotHistoryLimit, 1719 ) 1720 1721 // execute request 1722 actualResp, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight) 1723 1724 // check response 1725 suite.checkResponse(actualResp, err) 1726 suite.assertAllExpectations() 1727 suite.Require().Equal(expectedResp, actualResp) 1728 }) 1729 1730 suite.Run("valid request with max_height > last_sealed_block_height", func() { 1731 headHeight = maxHeight - 1 1732 setupHeadHeight(headHeight) 1733 blockHeaders, _, nodeIdentities = setupStorage(minHeight, headHeight) 1734 expectedResp := setupExecClient() 1735 fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() 1736 1737 backend := New( 1738 state, 1739 nil, 1740 nil, 1741 suite.blocks, 1742 suite.headers, 1743 nil, 1744 nil, 1745 suite.receipts, 1746 suite.results, 1747 suite.chainID, 1748 metrics.NewNoopCollector(), 1749 connFactory, // the connection factory should be used to get the execution node client 1750 false, 1751 DefaultMaxHeightRange, 1752 nil, 1753 fixedENIdentifiersStr, 1754 suite.log, 1755 DefaultSnapshotHistoryLimit, 1756 ) 1757 1758 actualResp, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight) 1759 suite.checkResponse(actualResp, err) 1760 1761 suite.assertAllExpectations() 1762 suite.Require().Equal(expectedResp, actualResp) 1763 }) 1764 1765 // set max height range to 1 and request range of 2 1766 suite.Run("invalid request exceeding max height range", func() { 1767 headHeight = maxHeight - 1 1768 setupHeadHeight(headHeight) 1769 blockHeaders, _, nodeIdentities = setupStorage(minHeight, headHeight) 1770 fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() 1771 1772 // create handler 1773 backend := New( 1774 state, 1775 nil, 1776 nil, 1777 suite.blocks, 1778 suite.headers, 1779 nil, 1780 nil, 1781 suite.receipts, 1782 suite.results, 1783 suite.chainID, 1784 metrics.NewNoopCollector(), 1785 connFactory, // the connection factory should be used to get the execution node client 1786 false, 1787 1, // set maximum range to 1 1788 nil, 1789 fixedENIdentifiersStr, 1790 suite.log, 1791 DefaultSnapshotHistoryLimit, 1792 ) 1793 1794 _, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, minHeight+1) 1795 suite.Require().Error(err) 1796 }) 1797 1798 suite.Run("invalid request last_sealed_block_height < min height", func() { 1799 1800 // set sealed height to one less than the request start height 1801 headHeight = minHeight - 1 1802 1803 // setup mocks 1804 setupHeadHeight(headHeight) 1805 blockHeaders, _, nodeIdentities = setupStorage(minHeight, maxHeight) 1806 fixedENIdentifiersStr := flow.IdentifierList(nodeIdentities.NodeIDs()).Strings() 1807 1808 // create handler 1809 backend := New( 1810 state, 1811 nil, 1812 nil, 1813 suite.blocks, 1814 suite.headers, 1815 nil, 1816 nil, 1817 suite.receipts, 1818 suite.results, 1819 suite.chainID, 1820 metrics.NewNoopCollector(), 1821 connFactory, // the connection factory should be used to get the execution node client 1822 false, 1823 DefaultMaxHeightRange, 1824 nil, 1825 fixedENIdentifiersStr, 1826 suite.log, 1827 DefaultSnapshotHistoryLimit, 1828 ) 1829 1830 _, err := backend.GetEventsForHeightRange(ctx, string(flow.EventAccountCreated), minHeight, maxHeight) 1831 suite.Require().Error(err) 1832 }) 1833 1834 } 1835 1836 func (suite *Suite) TestGetAccount() { 1837 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1838 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 1839 1840 address, err := suite.chainID.Chain().NewAddressGenerator().NextAddress() 1841 suite.Require().NoError(err) 1842 1843 account := &entitiesproto.Account{ 1844 Address: address.Bytes(), 1845 } 1846 ctx := context.Background() 1847 1848 // setup the latest sealed block 1849 block := unittest.BlockFixture() 1850 header := block.Header // create a mock header 1851 seal := unittest.Seal.Fixture() // create a mock seal 1852 seal.BlockID = header.ID() // make the seal point to the header 1853 1854 suite.snapshot. 1855 On("Head"). 1856 Return(header, nil). 1857 Once() 1858 1859 // create the expected execution API request 1860 blockID := header.ID() 1861 exeReq := &execproto.GetAccountAtBlockIDRequest{ 1862 BlockId: blockID[:], 1863 Address: address.Bytes(), 1864 } 1865 1866 // create the expected execution API response 1867 exeResp := &execproto.GetAccountAtBlockIDResponse{ 1868 Account: account, 1869 } 1870 1871 // setup the execution client mock 1872 suite.execClient. 1873 On("GetAccountAtBlockID", ctx, exeReq). 1874 Return(exeResp, nil). 1875 Once() 1876 1877 receipts, ids := suite.setupReceipts(&block) 1878 1879 suite.snapshot.On("Identities", mock.Anything).Return(ids, nil) 1880 // create a mock connection factory 1881 connFactory := new(backendmock.ConnectionFactory) 1882 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 1883 1884 // create the handler with the mock 1885 backend := New( 1886 suite.state, 1887 nil, 1888 nil, 1889 nil, 1890 suite.headers, 1891 nil, 1892 nil, 1893 suite.receipts, 1894 suite.results, 1895 suite.chainID, 1896 metrics.NewNoopCollector(), 1897 connFactory, // the connection factory should be used to get the execution node client 1898 false, 1899 DefaultMaxHeightRange, 1900 nil, 1901 nil, 1902 suite.log, 1903 DefaultSnapshotHistoryLimit, 1904 ) 1905 1906 preferredENIdentifiers = flow.IdentifierList{receipts[0].ExecutorID} 1907 1908 suite.Run("happy path - valid request and valid response", func() { 1909 account, err := backend.GetAccountAtLatestBlock(ctx, address) 1910 suite.checkResponse(account, err) 1911 1912 suite.Require().Equal(address, account.Address) 1913 1914 suite.assertAllExpectations() 1915 }) 1916 } 1917 1918 func (suite *Suite) TestGetAccountAtBlockHeight() { 1919 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 1920 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 1921 1922 height := uint64(5) 1923 address := unittest.AddressFixture() 1924 account := &entitiesproto.Account{ 1925 Address: address.Bytes(), 1926 } 1927 ctx := context.Background() 1928 1929 // create a mock block header 1930 b := unittest.BlockFixture() 1931 h := b.Header 1932 1933 // setup headers storage to return the header when queried by height 1934 suite.headers. 1935 On("ByHeight", height). 1936 Return(h, nil). 1937 Once() 1938 1939 receipts, ids := suite.setupReceipts(&b) 1940 suite.snapshot.On("Identities", mock.Anything).Return(ids, nil) 1941 1942 // create a mock connection factory 1943 connFactory := new(backendmock.ConnectionFactory) 1944 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 1945 1946 // create the expected execution API request 1947 blockID := h.ID() 1948 exeReq := &execproto.GetAccountAtBlockIDRequest{ 1949 BlockId: blockID[:], 1950 Address: address.Bytes(), 1951 } 1952 1953 // create the expected execution API response 1954 exeResp := &execproto.GetAccountAtBlockIDResponse{ 1955 Account: account, 1956 } 1957 1958 // setup the execution client mock 1959 suite.execClient. 1960 On("GetAccountAtBlockID", ctx, exeReq). 1961 Return(exeResp, nil). 1962 Once() 1963 1964 // create the handler with the mock 1965 backend := New( 1966 suite.state, 1967 nil, 1968 nil, 1969 nil, 1970 suite.headers, 1971 nil, 1972 nil, 1973 suite.receipts, 1974 suite.results, 1975 flow.Testnet, 1976 metrics.NewNoopCollector(), 1977 connFactory, // the connection factory should be used to get the execution node client 1978 false, 1979 DefaultMaxHeightRange, 1980 nil, 1981 nil, 1982 suite.log, 1983 DefaultSnapshotHistoryLimit, 1984 ) 1985 1986 preferredENIdentifiers = flow.IdentifierList{receipts[0].ExecutorID} 1987 1988 suite.Run("happy path - valid request and valid response", func() { 1989 account, err := backend.GetAccountAtBlockHeight(ctx, address, height) 1990 suite.checkResponse(account, err) 1991 1992 suite.Require().Equal(address, account.Address) 1993 1994 suite.assertAllExpectations() 1995 }) 1996 } 1997 1998 func (suite *Suite) TestGetNetworkParameters() { 1999 suite.state.On("Sealed").Return(suite.snapshot, nil).Maybe() 2000 2001 expectedChainID := flow.Mainnet 2002 2003 backend := New(nil, 2004 nil, 2005 nil, 2006 nil, 2007 nil, 2008 nil, 2009 nil, 2010 nil, 2011 nil, 2012 flow.Mainnet, 2013 metrics.NewNoopCollector(), 2014 nil, 2015 false, 2016 DefaultMaxHeightRange, 2017 nil, 2018 nil, 2019 suite.log, 2020 DefaultSnapshotHistoryLimit, 2021 ) 2022 2023 params := backend.GetNetworkParameters(context.Background()) 2024 2025 suite.Require().Equal(expectedChainID, params.ChainID) 2026 } 2027 2028 // TestExecutionNodesForBlockID tests the common method backend.executionNodesForBlockID used for serving all API calls 2029 // that need to talk to an execution node. 2030 func (suite *Suite) TestExecutionNodesForBlockID() { 2031 2032 totalReceipts := 5 2033 2034 block := unittest.BlockFixture() 2035 2036 // generate one execution node identities for each receipt assuming that each ER is generated by a unique exec node 2037 allExecutionNodes := unittest.IdentityListFixture(totalReceipts, unittest.WithRole(flow.RoleExecution)) 2038 2039 // one execution result for all receipts for this block 2040 executionResult := unittest.ExecutionResultFixture() 2041 2042 // generate execution receipts 2043 receipts := make(flow.ExecutionReceiptList, totalReceipts) 2044 for j := 0; j < totalReceipts; j++ { 2045 r := unittest.ReceiptForBlockFixture(&block) 2046 r.ExecutorID = allExecutionNodes[j].NodeID 2047 er := *executionResult 2048 r.ExecutionResult = er 2049 receipts[j] = r 2050 } 2051 2052 currentAttempt := 0 2053 attempt1Receipts, attempt2Receipts, attempt3Receipts := receipts, receipts, receipts 2054 2055 // setup receipts storage mock to return different list of receipts on each call 2056 suite.receipts. 2057 On("ByBlockID", block.ID()).Return( 2058 func(id flow.Identifier) flow.ExecutionReceiptList { 2059 switch currentAttempt { 2060 case 0: 2061 currentAttempt++ 2062 return attempt1Receipts 2063 case 1: 2064 currentAttempt++ 2065 return attempt2Receipts 2066 default: 2067 currentAttempt = 0 2068 return attempt3Receipts 2069 } 2070 }, 2071 func(id flow.Identifier) error { return nil }) 2072 2073 suite.snapshot.On("Identities", mock.Anything).Return( 2074 func(filter flow.IdentityFilter) flow.IdentityList { 2075 // apply the filter passed in to the list of all the execution nodes 2076 return allExecutionNodes.Filter(filter) 2077 }, 2078 func(flow.IdentityFilter) error { return nil }) 2079 suite.state.On("Final").Return(suite.snapshot, nil).Maybe() 2080 2081 testExecutionNodesForBlockID := func(preferredENs, fixedENs, expectedENs flow.IdentityList) { 2082 2083 if preferredENs != nil { 2084 preferredENIdentifiers = preferredENs.NodeIDs() 2085 } 2086 if fixedENs != nil { 2087 fixedENIdentifiers = fixedENs.NodeIDs() 2088 } 2089 actualList, err := executionNodesForBlockID(context.Background(), block.ID(), suite.receipts, suite.state, suite.log) 2090 require.NoError(suite.T(), err) 2091 if expectedENs == nil { 2092 expectedENs = flow.IdentityList{} 2093 } 2094 if len(expectedENs) > maxExecutionNodesCnt { 2095 for _, actual := range actualList { 2096 require.Contains(suite.T(), expectedENs, actual) 2097 } 2098 } else { 2099 require.ElementsMatch(suite.T(), actualList, expectedENs) 2100 } 2101 } 2102 // if we don't find sufficient receipts, executionNodesForBlockID should return a list of random ENs 2103 suite.Run("insufficient receipts return random ENs in state", func() { 2104 // return no receipts at all attempts 2105 attempt1Receipts = flow.ExecutionReceiptList{} 2106 attempt2Receipts = flow.ExecutionReceiptList{} 2107 attempt3Receipts = flow.ExecutionReceiptList{} 2108 suite.state.On("AtBlockID", mock.Anything).Return(suite.snapshot) 2109 actualList, err := executionNodesForBlockID(context.Background(), block.ID(), suite.receipts, suite.state, suite.log) 2110 require.NoError(suite.T(), err) 2111 require.Equal(suite.T(), len(actualList), maxExecutionNodesCnt) 2112 }) 2113 2114 // if no preferred or fixed ENs are specified, the ExecutionNodesForBlockID function should 2115 // return the exe node list without a filter 2116 suite.Run("no preferred or fixed ENs", func() { 2117 testExecutionNodesForBlockID(nil, nil, allExecutionNodes) 2118 }) 2119 // if only preferred ENs are specified, the ExecutionNodesForBlockID function should 2120 // return the preferred ENs list 2121 suite.Run("two preferred ENs with zero fixed EN", func() { 2122 // mark the first two ENs as preferred 2123 preferredENs := allExecutionNodes[0:2] 2124 expectedList := preferredENs 2125 testExecutionNodesForBlockID(preferredENs, nil, expectedList) 2126 }) 2127 // if only fixed ENs are specified, the ExecutionNodesForBlockID function should 2128 // return the fixed ENs list 2129 suite.Run("two fixed ENs with zero preferred EN", func() { 2130 // mark the first two ENs as fixed 2131 fixedENs := allExecutionNodes[0:2] 2132 expectedList := fixedENs 2133 testExecutionNodesForBlockID(nil, fixedENs, expectedList) 2134 }) 2135 // if both are specified, the ExecutionNodesForBlockID function should 2136 // return the preferred ENs list 2137 suite.Run("four fixed ENs of which two are preferred ENs", func() { 2138 // mark the first four ENs as fixed 2139 fixedENs := allExecutionNodes[0:5] 2140 // mark the first two of the fixed ENs as preferred ENs 2141 preferredENs := fixedENs[0:2] 2142 expectedList := preferredENs 2143 testExecutionNodesForBlockID(preferredENs, fixedENs, expectedList) 2144 }) 2145 // if both are specified, but the preferred ENs don't match the ExecutorIDs in the ER, 2146 // the ExecutionNodesForBlockID function should return the fixed ENs list 2147 suite.Run("four fixed ENs of which two are preferred ENs but have not generated the ER", func() { 2148 // mark the first two ENs as fixed 2149 fixedENs := allExecutionNodes[0:2] 2150 // specify two ENs not specified in the ERs as preferred 2151 preferredENs := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 2152 expectedList := fixedENs 2153 testExecutionNodesForBlockID(preferredENs, fixedENs, expectedList) 2154 }) 2155 // if execution receipts are not yet available, the ExecutionNodesForBlockID function should retry twice 2156 suite.Run("retry execution receipt query", func() { 2157 // on first attempt, no execution receipts are available 2158 attempt1Receipts = flow.ExecutionReceiptList{} 2159 // on second attempt ony one is available 2160 attempt2Receipts = flow.ExecutionReceiptList{receipts[0]} 2161 // on third attempt all receipts are available 2162 attempt3Receipts = receipts 2163 currentAttempt = 0 2164 // mark the first two ENs as preferred 2165 preferredENs := allExecutionNodes[0:2] 2166 expectedList := preferredENs 2167 testExecutionNodesForBlockID(preferredENs, nil, expectedList) 2168 }) 2169 } 2170 2171 // TestExecuteScriptOnExecutionNode tests the method backend.scripts.executeScriptOnExecutionNode for script execution 2172 func (suite *Suite) TestExecuteScriptOnExecutionNode() { 2173 2174 // create a mock connection factory 2175 connFactory := new(backendmock.ConnectionFactory) 2176 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 2177 connFactory.On("InvalidateExecutionAPIClient", mock.Anything) 2178 2179 // create the handler with the mock 2180 backend := New( 2181 suite.state, 2182 nil, 2183 nil, 2184 nil, 2185 suite.headers, 2186 nil, 2187 nil, 2188 suite.receipts, 2189 suite.results, 2190 flow.Mainnet, 2191 metrics.NewNoopCollector(), 2192 connFactory, // the connection factory should be used to get the execution node client 2193 false, 2194 DefaultMaxHeightRange, 2195 nil, 2196 nil, 2197 suite.log, 2198 DefaultSnapshotHistoryLimit, 2199 ) 2200 2201 // mock parameters 2202 ctx := context.Background() 2203 block := unittest.BlockFixture() 2204 blockID := block.ID() 2205 script := []byte("dummy script") 2206 arguments := [][]byte(nil) 2207 executionNode := unittest.IdentityFixture(unittest.WithRole(flow.RoleExecution)) 2208 execReq := &execproto.ExecuteScriptAtBlockIDRequest{ 2209 BlockId: blockID[:], 2210 Script: script, 2211 Arguments: arguments, 2212 } 2213 execRes := &execproto.ExecuteScriptAtBlockIDResponse{ 2214 Value: []byte{4, 5, 6}, 2215 } 2216 2217 suite.Run("happy path script execution success", func() { 2218 suite.execClient.On("ExecuteScriptAtBlockID", ctx, execReq).Return(execRes, nil).Once() 2219 res, err := backend.tryExecuteScript(ctx, executionNode, execReq) 2220 suite.execClient.AssertExpectations(suite.T()) 2221 suite.checkResponse(res, err) 2222 }) 2223 2224 suite.Run("script execution failure returns status OK", func() { 2225 suite.execClient.On("ExecuteScriptAtBlockID", ctx, execReq). 2226 Return(nil, status.Error(codes.InvalidArgument, "execution failure!")).Once() 2227 _, err := backend.tryExecuteScript(ctx, executionNode, execReq) 2228 suite.execClient.AssertExpectations(suite.T()) 2229 suite.Require().Error(err) 2230 suite.Require().Equal(status.Code(err), codes.InvalidArgument) 2231 }) 2232 2233 suite.Run("execution node internal failure returns status code Internal", func() { 2234 suite.execClient.On("ExecuteScriptAtBlockID", ctx, execReq). 2235 Return(nil, status.Error(codes.Internal, "execution node internal error!")).Once() 2236 _, err := backend.tryExecuteScript(ctx, executionNode, execReq) 2237 suite.execClient.AssertExpectations(suite.T()) 2238 suite.Require().Error(err) 2239 suite.Require().Equal(status.Code(err), codes.Internal) 2240 }) 2241 } 2242 2243 func (suite *Suite) assertAllExpectations() { 2244 suite.snapshot.AssertExpectations(suite.T()) 2245 suite.state.AssertExpectations(suite.T()) 2246 suite.blocks.AssertExpectations(suite.T()) 2247 suite.headers.AssertExpectations(suite.T()) 2248 suite.collections.AssertExpectations(suite.T()) 2249 suite.transactions.AssertExpectations(suite.T()) 2250 suite.execClient.AssertExpectations(suite.T()) 2251 } 2252 2253 func (suite *Suite) checkResponse(resp interface{}, err error) { 2254 suite.Require().NoError(err) 2255 suite.Require().NotNil(resp) 2256 } 2257 2258 func (suite *Suite) setupReceipts(block *flow.Block) ([]*flow.ExecutionReceipt, flow.IdentityList) { 2259 ids := unittest.IdentityListFixture(2, unittest.WithRole(flow.RoleExecution)) 2260 receipt1 := unittest.ReceiptForBlockFixture(block) 2261 receipt1.ExecutorID = ids[0].NodeID 2262 receipt2 := unittest.ReceiptForBlockFixture(block) 2263 receipt2.ExecutorID = ids[1].NodeID 2264 receipt1.ExecutionResult = receipt2.ExecutionResult 2265 2266 receipts := flow.ExecutionReceiptList{receipt1, receipt2} 2267 suite.receipts. 2268 On("ByBlockID", block.ID()). 2269 Return(receipts, nil) 2270 2271 return receipts, ids 2272 } 2273 2274 func (suite *Suite) setupConnectionFactory() ConnectionFactory { 2275 // create a mock connection factory 2276 connFactory := new(backendmock.ConnectionFactory) 2277 connFactory.On("GetExecutionAPIClient", mock.Anything).Return(suite.execClient, &mockCloser{}, nil) 2278 connFactory.On("InvalidateExecutionAPIClient", mock.Anything) 2279 return connFactory 2280 } 2281 2282 func getEvents(n int) []flow.Event { 2283 events := make([]flow.Event, n) 2284 for i := range events { 2285 events[i] = flow.Event{Type: flow.EventAccountCreated} 2286 } 2287 return events 2288 }