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