github.com/koko1123/flow-go-1@v0.29.6/engine/access/rpc/backend/backend_transactions.go (about) 1 package backend 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 "github.com/hashicorp/go-multierror" 10 accessproto "github.com/onflow/flow/protobuf/go/flow/access" 11 "github.com/onflow/flow/protobuf/go/flow/entities" 12 execproto "github.com/onflow/flow/protobuf/go/flow/execution" 13 "github.com/rs/zerolog" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 17 "github.com/koko1123/flow-go-1/access" 18 "github.com/koko1123/flow-go-1/engine/common/rpc" 19 "github.com/koko1123/flow-go-1/engine/common/rpc/convert" 20 "github.com/koko1123/flow-go-1/fvm/blueprints" 21 "github.com/koko1123/flow-go-1/model/flow" 22 "github.com/koko1123/flow-go-1/module" 23 "github.com/koko1123/flow-go-1/state/protocol" 24 "github.com/koko1123/flow-go-1/storage" 25 ) 26 27 const collectionNodesToTry uint = 3 28 29 type backendTransactions struct { 30 staticCollectionRPC accessproto.AccessAPIClient // rpc client tied to a fixed collection node 31 transactions storage.Transactions 32 executionReceipts storage.ExecutionReceipts 33 collections storage.Collections 34 blocks storage.Blocks 35 state protocol.State 36 chainID flow.ChainID 37 transactionMetrics module.TransactionMetrics 38 transactionValidator *access.TransactionValidator 39 retry *Retry 40 connFactory ConnectionFactory 41 42 previousAccessNodes []accessproto.AccessAPIClient 43 log zerolog.Logger 44 } 45 46 // SendTransaction forwards the transaction to the collection node 47 func (b *backendTransactions) SendTransaction( 48 ctx context.Context, 49 tx *flow.TransactionBody, 50 ) error { 51 now := time.Now().UTC() 52 53 err := b.transactionValidator.Validate(tx) 54 if err != nil { 55 return status.Errorf(codes.InvalidArgument, "invalid transaction: %s", err.Error()) 56 } 57 58 // send the transaction to the collection node if valid 59 err = b.trySendTransaction(ctx, tx) 60 if err != nil { 61 b.transactionMetrics.TransactionSubmissionFailed() 62 return status.Error(codes.Internal, fmt.Sprintf("failed to send transaction to a collection node: %v", err)) 63 } 64 65 b.transactionMetrics.TransactionReceived(tx.ID(), now) 66 67 // store the transaction locally 68 err = b.transactions.Store(tx) 69 if err != nil { 70 // TODO: why would this be InvalidArgument? 71 return status.Error(codes.InvalidArgument, fmt.Sprintf("failed to store transaction: %v", err)) 72 } 73 74 if b.retry.IsActive() { 75 go b.registerTransactionForRetry(tx) 76 } 77 78 return nil 79 } 80 81 // trySendTransaction tries to transaction to a collection node 82 func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.TransactionBody) error { 83 84 // if a collection node rpc client was provided at startup, just use that 85 if b.staticCollectionRPC != nil { 86 return b.grpcTxSend(ctx, b.staticCollectionRPC, tx) 87 } 88 89 // otherwise choose a random set of collections nodes to try 90 collAddrs, err := b.chooseCollectionNodes(tx, collectionNodesToTry) 91 if err != nil { 92 return fmt.Errorf("failed to determine collection node for tx %x: %w", tx, err) 93 } 94 95 var sendErrors *multierror.Error 96 logAnyError := func() { 97 err = sendErrors.ErrorOrNil() 98 if err != nil { 99 b.log.Info().Err(err).Msg("failed to send transactions to collector nodes") 100 } 101 } 102 defer logAnyError() 103 104 // try sending the transaction to one of the chosen collection nodes 105 for _, addr := range collAddrs { 106 err = b.sendTransactionToCollector(ctx, tx, addr) 107 if err == nil { 108 return nil 109 } 110 sendErrors = multierror.Append(sendErrors, err) 111 } 112 113 return sendErrors.ErrorOrNil() 114 } 115 116 // chooseCollectionNodes finds a random subset of size sampleSize of collection node addresses from the 117 // collection node cluster responsible for the given tx 118 func (b *backendTransactions) chooseCollectionNodes(tx *flow.TransactionBody, sampleSize uint) ([]string, error) { 119 120 // retrieve the set of collector clusters 121 clusters, err := b.state.Final().Epochs().Current().Clustering() 122 if err != nil { 123 return nil, fmt.Errorf("could not cluster collection nodes: %w", err) 124 } 125 126 // get the cluster responsible for the transaction 127 txCluster, ok := clusters.ByTxID(tx.ID()) 128 if !ok { 129 return nil, fmt.Errorf("could not get local cluster by txID: %x", tx.ID()) 130 } 131 132 // select a random subset of collection nodes from the cluster to be tried in order 133 targetNodes := txCluster.Sample(sampleSize) 134 135 // collect the addresses of all the chosen collection nodes 136 var targetAddrs = make([]string, len(targetNodes)) 137 for i, id := range targetNodes { 138 targetAddrs[i] = id.Address 139 } 140 141 return targetAddrs, nil 142 } 143 144 // sendTransactionToCollection sends the transaction to the given collection node via grpc 145 func (b *backendTransactions) sendTransactionToCollector(ctx context.Context, 146 tx *flow.TransactionBody, 147 collectionNodeAddr string) error { 148 149 collectionRPC, closer, err := b.connFactory.GetAccessAPIClient(collectionNodeAddr) 150 if err != nil { 151 return fmt.Errorf("failed to connect to collection node at %s: %w", collectionNodeAddr, err) 152 } 153 defer closer.Close() 154 155 err = b.grpcTxSend(ctx, collectionRPC, tx) 156 if err != nil { 157 if status.Code(err) == codes.Unavailable { 158 b.connFactory.InvalidateAccessAPIClient(collectionNodeAddr) 159 } 160 return fmt.Errorf("failed to send transaction to collection node at %s: %v", collectionNodeAddr, err) 161 } 162 return nil 163 } 164 165 func (b *backendTransactions) grpcTxSend(ctx context.Context, client accessproto.AccessAPIClient, tx *flow.TransactionBody) error { 166 colReq := &accessproto.SendTransactionRequest{ 167 Transaction: convert.TransactionToMessage(*tx), 168 } 169 170 clientDeadline := time.Now().Add(time.Duration(2) * time.Second) 171 ctx, cancel := context.WithDeadline(ctx, clientDeadline) 172 defer cancel() 173 _, err := client.SendTransaction(ctx, colReq) 174 return err 175 } 176 177 // SendRawTransaction sends a raw transaction to the collection node 178 func (b *backendTransactions) SendRawTransaction( 179 ctx context.Context, 180 tx *flow.TransactionBody, 181 ) error { 182 183 // send the transaction to the collection node 184 return b.trySendTransaction(ctx, tx) 185 } 186 187 func (b *backendTransactions) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.TransactionBody, error) { 188 // look up transaction from storage 189 tx, err := b.transactions.ByID(txID) 190 txErr := rpc.ConvertStorageError(err) 191 192 if txErr != nil { 193 if status.Code(txErr) == codes.NotFound { 194 return b.getHistoricalTransaction(ctx, txID) 195 } 196 // Other Error trying to retrieve the transaction, return with err 197 return nil, txErr 198 } 199 200 return tx, nil 201 } 202 203 func (b *backendTransactions) GetTransactionsByBlockID( 204 ctx context.Context, 205 blockID flow.Identifier, 206 ) ([]*flow.TransactionBody, error) { 207 var transactions []*flow.TransactionBody 208 209 // TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID 210 block, err := b.blocks.ByID(blockID) 211 if err != nil { 212 return nil, rpc.ConvertStorageError(err) 213 } 214 215 for _, guarantee := range block.Payload.Guarantees { 216 collection, err := b.collections.ByID(guarantee.CollectionID) 217 if err != nil { 218 return nil, rpc.ConvertStorageError(err) 219 } 220 221 transactions = append(transactions, collection.Transactions...) 222 } 223 224 systemTx, err := blueprints.SystemChunkTransaction(b.chainID.Chain()) 225 if err != nil { 226 return nil, fmt.Errorf("could not get system chunk transaction: %w", err) 227 } 228 229 transactions = append(transactions, systemTx) 230 231 return transactions, nil 232 } 233 234 func (b *backendTransactions) GetTransactionResult( 235 ctx context.Context, 236 txID flow.Identifier, 237 ) (*access.TransactionResult, error) { 238 // look up transaction from storage 239 start := time.Now() 240 tx, err := b.transactions.ByID(txID) 241 242 txErr := rpc.ConvertStorageError(err) 243 if txErr != nil { 244 if status.Code(txErr) == codes.NotFound { 245 // Tx not found. If we have historical Sporks setup, lets look through those as well 246 historicalTxResult, err := b.getHistoricalTransactionResult(ctx, txID) 247 if err != nil { 248 // if tx not found in old access nodes either, then assume that the tx was submitted to a different AN 249 // and return status as unknown 250 txStatus := flow.TransactionStatusUnknown 251 return &access.TransactionResult{ 252 Status: txStatus, 253 StatusCode: uint(txStatus), 254 }, nil 255 } 256 return historicalTxResult, nil 257 } 258 return nil, txErr 259 } 260 261 // find the block for the transaction 262 block, err := b.lookupBlock(txID) 263 if err != nil && !errors.Is(err, storage.ErrNotFound) { 264 return nil, rpc.ConvertStorageError(err) 265 } 266 267 var blockID flow.Identifier 268 var transactionWasExecuted bool 269 var events []flow.Event 270 var txError string 271 var statusCode uint32 272 var blockHeight uint64 273 // access node may not have the block if it hasn't yet been finalized, hence block can be nil at this point 274 if block != nil { 275 blockID = block.ID() 276 transactionWasExecuted, events, statusCode, txError, err = b.lookupTransactionResult(ctx, txID, blockID) 277 blockHeight = block.Header.Height 278 if err != nil { 279 return nil, rpc.ConvertStorageError(err) 280 } 281 } 282 283 // derive status of the transaction 284 txStatus, err := b.deriveTransactionStatus(tx, transactionWasExecuted, block) 285 if err != nil { 286 return nil, rpc.ConvertStorageError(err) 287 } 288 289 b.transactionMetrics.TransactionResultFetched(time.Since(start), len(tx.Script)) 290 291 return &access.TransactionResult{ 292 Status: txStatus, 293 StatusCode: uint(statusCode), 294 Events: events, 295 ErrorMessage: txError, 296 BlockID: blockID, 297 TransactionID: txID, 298 BlockHeight: blockHeight, 299 }, nil 300 } 301 302 func (b *backendTransactions) GetTransactionResultsByBlockID( 303 ctx context.Context, 304 blockID flow.Identifier, 305 ) ([]*access.TransactionResult, error) { 306 // TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID 307 block, err := b.blocks.ByID(blockID) 308 if err != nil { 309 return nil, rpc.ConvertStorageError(err) 310 } 311 312 req := &execproto.GetTransactionsByBlockIDRequest{ 313 BlockId: blockID[:], 314 } 315 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 316 if err != nil { 317 _, isInsufficientExecReceipts := err.(*InsufficientExecutionReceipts) 318 if isInsufficientExecReceipts { 319 return nil, status.Errorf(codes.NotFound, err.Error()) 320 } 321 return nil, status.Errorf(codes.Internal, "failed to retrieve results from any execution node: %v", err) 322 } 323 324 resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req) 325 if err != nil { 326 if status.Code(err) == codes.NotFound { 327 return nil, err 328 } 329 return nil, status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err) 330 } 331 332 results := make([]*access.TransactionResult, 0, len(resp.TransactionResults)) 333 i := 0 334 errInsufficientResults := status.Errorf( 335 codes.Internal, 336 "number of transaction results returned by execution node is less than the number of transactions in the block", 337 ) 338 339 for _, guarantee := range block.Payload.Guarantees { 340 collection, err := b.collections.LightByID(guarantee.CollectionID) 341 if err != nil { 342 return nil, rpc.ConvertStorageError(err) 343 } 344 345 for _, txID := range collection.Transactions { 346 if i >= len(resp.TransactionResults) { 347 return nil, errInsufficientResults 348 } 349 350 txResult := resp.TransactionResults[i] 351 // tx body is irrelevant to status if it's in an executed block 352 txStatus, err := b.deriveTransactionStatus(nil, true, block) 353 if err != nil { 354 return nil, rpc.ConvertStorageError(err) 355 } 356 357 results = append(results, &access.TransactionResult{ 358 Status: txStatus, 359 StatusCode: uint(txResult.GetStatusCode()), 360 Events: convert.MessagesToEvents(txResult.GetEvents()), 361 ErrorMessage: txResult.GetErrorMessage(), 362 BlockID: blockID, 363 TransactionID: txID, 364 CollectionID: guarantee.CollectionID, 365 BlockHeight: block.Header.Height, 366 }) 367 368 i++ 369 } 370 } 371 372 rootBlock, err := b.state.Params().Root() 373 if err != nil { 374 return nil, status.Errorf(codes.Internal, "failed to retrieve root block: %v", err) 375 } 376 377 // root block has no system transaction result 378 if rootBlock.ID() != blockID { 379 // system chunk transaction 380 if i >= len(resp.TransactionResults) { 381 return nil, errInsufficientResults 382 } else if i < len(resp.TransactionResults)-1 { 383 return nil, status.Errorf(codes.Internal, "number of transaction results returned by execution node is more than the number of transactions in the block") 384 } 385 386 systemTx, err := blueprints.SystemChunkTransaction(b.chainID.Chain()) 387 if err != nil { 388 return nil, fmt.Errorf("could not get system chunk transaction: %w", err) 389 } 390 systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1] 391 systemTxStatus, err := b.deriveTransactionStatus(systemTx, true, block) 392 if err != nil { 393 return nil, rpc.ConvertStorageError(err) 394 } 395 396 results = append(results, &access.TransactionResult{ 397 Status: systemTxStatus, 398 StatusCode: uint(systemTxResult.GetStatusCode()), 399 Events: convert.MessagesToEvents(systemTxResult.GetEvents()), 400 ErrorMessage: systemTxResult.GetErrorMessage(), 401 BlockID: blockID, 402 TransactionID: systemTx.ID(), 403 BlockHeight: block.Header.Height, 404 }) 405 } 406 407 return results, nil 408 } 409 410 // GetTransactionResultByIndex returns TransactionsResults for an index in a block that is executed, 411 // pending or finalized transactions return errors 412 func (b *backendTransactions) GetTransactionResultByIndex( 413 ctx context.Context, 414 blockID flow.Identifier, 415 index uint32, 416 ) (*access.TransactionResult, error) { 417 // TODO: https://github.com/koko1123/flow-go-1/issues/2175 so caching doesn't cause a circular dependency 418 block, err := b.blocks.ByID(blockID) 419 if err != nil { 420 return nil, rpc.ConvertStorageError(err) 421 } 422 423 // create request and forward to EN 424 req := &execproto.GetTransactionByIndexRequest{ 425 BlockId: blockID[:], 426 Index: index, 427 } 428 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 429 if err != nil { 430 _, isInsufficientExecReceipts := err.(*InsufficientExecutionReceipts) 431 if isInsufficientExecReceipts { 432 return nil, status.Errorf(codes.NotFound, err.Error()) 433 } 434 return nil, status.Errorf(codes.Internal, "failed to retrieve result from any execution node: %v", err) 435 } 436 437 resp, err := b.getTransactionResultByIndexFromAnyExeNode(ctx, execNodes, req) 438 if err != nil { 439 if status.Code(err) == codes.NotFound { 440 return nil, err 441 } 442 return nil, status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err) 443 } 444 445 // tx body is irrelevant to status if it's in an executed block 446 txStatus, err := b.deriveTransactionStatus(nil, true, block) 447 if err != nil { 448 return nil, rpc.ConvertStorageError(err) 449 } 450 451 // convert to response, cache and return 452 return &access.TransactionResult{ 453 Status: txStatus, 454 StatusCode: uint(resp.GetStatusCode()), 455 Events: convert.MessagesToEvents(resp.GetEvents()), 456 ErrorMessage: resp.GetErrorMessage(), 457 BlockID: blockID, 458 BlockHeight: block.Header.Height, 459 }, nil 460 } 461 462 // deriveTransactionStatus derives the transaction status based on current protocol state 463 func (b *backendTransactions) deriveTransactionStatus( 464 tx *flow.TransactionBody, 465 executed bool, 466 block *flow.Block, 467 ) (flow.TransactionStatus, error) { 468 469 if block == nil { 470 // Not in a block, let's see if it's expired 471 referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head() 472 if err != nil { 473 return flow.TransactionStatusUnknown, err 474 } 475 refHeight := referenceBlock.Height 476 // get the latest finalized block from the state 477 finalized, err := b.state.Final().Head() 478 if err != nil { 479 return flow.TransactionStatusUnknown, err 480 } 481 finalizedHeight := finalized.Height 482 483 // if we haven't seen the expiry block for this transaction, it's not expired 484 if !b.isExpired(refHeight, finalizedHeight) { 485 return flow.TransactionStatusPending, nil 486 } 487 488 // At this point, we have seen the expiry block for the transaction. 489 // This means that, if no collections prior to the expiry block contain 490 // the transaction, it can never be included and is expired. 491 // 492 // To ensure this, we need to have received all collections up to the 493 // expiry block to ensure the transaction did not appear in any. 494 495 // the last full height is the height where we have received all 496 // collections for all blocks with a lower height 497 fullHeight, err := b.blocks.GetLastFullBlockHeight() 498 if err != nil { 499 return flow.TransactionStatusUnknown, err 500 } 501 502 // if we have received collections for all blocks up to the expiry block, the transaction is expired 503 if b.isExpired(refHeight, fullHeight) { 504 return flow.TransactionStatusExpired, err 505 } 506 507 // tx found in transaction storage and collection storage but not in block storage 508 // However, this will not happen as of now since the ingestion engine doesn't subscribe 509 // for collections 510 return flow.TransactionStatusPending, nil 511 } 512 513 if !executed { 514 // If we've gotten here, but the block has not yet been executed, report it as only been finalized 515 return flow.TransactionStatusFinalized, nil 516 } 517 518 // From this point on, we know for sure this transaction has at least been executed 519 520 // get the latest sealed block from the state 521 sealed, err := b.state.Sealed().Head() 522 if err != nil { 523 return flow.TransactionStatusUnknown, err 524 } 525 526 if block.Header.Height > sealed.Height { 527 // The block is not yet sealed, so we'll report it as only executed 528 return flow.TransactionStatusExecuted, nil 529 } 530 531 // otherwise, this block has been executed, and sealed, so report as sealed 532 return flow.TransactionStatusSealed, nil 533 } 534 535 // isExpired checks whether a transaction is expired given the height of the 536 // transaction's reference block and the height to compare against. 537 func (b *backendTransactions) isExpired(refHeight, compareToHeight uint64) bool { 538 if compareToHeight <= refHeight { 539 return false 540 } 541 return compareToHeight-refHeight > flow.DefaultTransactionExpiry 542 } 543 544 func (b *backendTransactions) lookupBlock(txID flow.Identifier) (*flow.Block, error) { 545 546 collection, err := b.collections.LightByTransactionID(txID) 547 if err != nil { 548 return nil, err 549 } 550 551 block, err := b.blocks.ByCollectionID(collection.ID()) 552 if err != nil { 553 return nil, err 554 } 555 556 return block, nil 557 } 558 559 func (b *backendTransactions) lookupTransactionResult( 560 ctx context.Context, 561 txID flow.Identifier, 562 blockID flow.Identifier, 563 ) (bool, []flow.Event, uint32, string, error) { 564 565 events, txStatus, message, err := b.getTransactionResultFromExecutionNode(ctx, blockID, txID[:]) 566 if err != nil { 567 // if either the execution node reported no results or the execution node could not be chosen 568 if status.Code(err) == codes.NotFound { 569 // No result yet, indicate that it has not been executed 570 return false, nil, 0, "", nil 571 } 572 // Other Error trying to retrieve the result, return with err 573 return false, nil, 0, "", err 574 } 575 576 // considered executed as long as some result is returned, even if it's an error message 577 return true, events, txStatus, message, nil 578 } 579 580 func (b *backendTransactions) getHistoricalTransaction( 581 ctx context.Context, 582 txID flow.Identifier, 583 ) (*flow.TransactionBody, error) { 584 for _, historicalNode := range b.previousAccessNodes { 585 txResp, err := historicalNode.GetTransaction(ctx, &accessproto.GetTransactionRequest{Id: txID[:]}) 586 if err == nil { 587 tx, err := convert.MessageToTransaction(txResp.Transaction, b.chainID.Chain()) 588 // Found on a historical node. Report 589 return &tx, err 590 } 591 // Otherwise, if not found, just continue 592 if status.Code(err) == codes.NotFound { 593 continue 594 } 595 // TODO should we do something if the error isn't not found? 596 } 597 return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID) 598 } 599 600 func (b *backendTransactions) getHistoricalTransactionResult( 601 ctx context.Context, 602 txID flow.Identifier, 603 ) (*access.TransactionResult, error) { 604 for _, historicalNode := range b.previousAccessNodes { 605 result, err := historicalNode.GetTransactionResult(ctx, &accessproto.GetTransactionRequest{Id: txID[:]}) 606 if err == nil { 607 // Found on a historical node. Report 608 if result.GetStatus() == entities.TransactionStatus_PENDING { 609 // This is on a historical node. No transactions from it will ever be 610 // executed, therefore we should consider this expired 611 result.Status = entities.TransactionStatus_EXPIRED 612 } else if result.GetStatus() == entities.TransactionStatus_UNKNOWN { 613 // We've moved to returning Status UNKNOWN instead of an error with the NotFound status, 614 // Therefore we should continue and look at the next access node for answers. 615 continue 616 } 617 return access.MessageToTransactionResult(result), nil 618 } 619 // Otherwise, if not found, just continue 620 if status.Code(err) == codes.NotFound { 621 continue 622 } 623 // TODO should we do something if the error isn't not found? 624 } 625 return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID) 626 } 627 628 func (b *backendTransactions) registerTransactionForRetry(tx *flow.TransactionBody) { 629 referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head() 630 if err != nil { 631 return 632 } 633 634 b.retry.RegisterTransaction(referenceBlock.Height, tx) 635 } 636 637 func (b *backendTransactions) getTransactionResultFromExecutionNode( 638 ctx context.Context, 639 blockID flow.Identifier, 640 transactionID []byte, 641 ) ([]flow.Event, uint32, string, error) { 642 643 // create an execution API request for events at blockID and transactionID 644 req := &execproto.GetTransactionResultRequest{ 645 BlockId: blockID[:], 646 TransactionId: transactionID, 647 } 648 649 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 650 if err != nil { 651 // if no execution receipt were found, return a NotFound GRPC error 652 if errors.As(err, &InsufficientExecutionReceipts{}) { 653 return nil, 0, "", status.Errorf(codes.NotFound, err.Error()) 654 } 655 return nil, 0, "", status.Errorf(codes.Internal, "failed to retrieve result from any execution node: %v", err) 656 } 657 658 resp, err := b.getTransactionResultFromAnyExeNode(ctx, execNodes, req) 659 if err != nil { 660 if status.Code(err) == codes.NotFound { 661 return nil, 0, "", err 662 } 663 return nil, 0, "", status.Errorf(codes.Internal, "failed to retrieve result from execution node: %v", err) 664 } 665 666 events := convert.MessagesToEvents(resp.GetEvents()) 667 668 return events, resp.GetStatusCode(), resp.GetErrorMessage(), nil 669 } 670 671 func (b *backendTransactions) NotifyFinalizedBlockHeight(height uint64) { 672 b.retry.Retry(height) 673 } 674 675 func (b *backendTransactions) getTransactionResultFromAnyExeNode( 676 ctx context.Context, 677 execNodes flow.IdentityList, 678 req *execproto.GetTransactionResultRequest, 679 ) (*execproto.GetTransactionResultResponse, error) { 680 var errs *multierror.Error 681 logAnyError := func() { 682 errToReturn := errs.ErrorOrNil() 683 if errToReturn != nil { 684 b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes") 685 } 686 } 687 defer logAnyError() 688 // try to execute the script on one of the execution nodes 689 for _, execNode := range execNodes { 690 resp, err := b.tryGetTransactionResult(ctx, execNode, req) 691 if err == nil { 692 b.log.Debug(). 693 Str("execution_node", execNode.String()). 694 Hex("block_id", req.GetBlockId()). 695 Hex("transaction_id", req.GetTransactionId()). 696 Msg("Successfully got transaction results from any node") 697 return resp, nil 698 } 699 if status.Code(err) == codes.NotFound { 700 return nil, err 701 } 702 errs = multierror.Append(errs, err) 703 } 704 return nil, errs.ErrorOrNil() 705 } 706 707 func (b *backendTransactions) tryGetTransactionResult( 708 ctx context.Context, 709 execNode *flow.Identity, 710 req *execproto.GetTransactionResultRequest, 711 ) (*execproto.GetTransactionResultResponse, error) { 712 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 713 if err != nil { 714 return nil, err 715 } 716 defer closer.Close() 717 718 resp, err := execRPCClient.GetTransactionResult(ctx, req) 719 if err != nil { 720 if status.Code(err) == codes.Unavailable { 721 b.connFactory.InvalidateExecutionAPIClient(execNode.Address) 722 } 723 return nil, err 724 } 725 return resp, err 726 } 727 728 func (b *backendTransactions) getTransactionResultsByBlockIDFromAnyExeNode( 729 ctx context.Context, 730 execNodes flow.IdentityList, 731 req *execproto.GetTransactionsByBlockIDRequest, 732 ) (*execproto.GetTransactionResultsResponse, error) { 733 var errs *multierror.Error 734 735 defer func() { 736 if err := errs.ErrorOrNil(); err != nil { 737 b.log.Err(errs).Msg("failed to get transaction results from execution nodes") 738 } 739 }() 740 741 // if we were passed 0 execution nodes add a specific error 742 if len(execNodes) == 0 { 743 return nil, errors.New("zero execution nodes") 744 } 745 746 for _, execNode := range execNodes { 747 resp, err := b.tryGetTransactionResultsByBlockID(ctx, execNode, req) 748 if err == nil { 749 b.log.Debug(). 750 Str("execution_node", execNode.String()). 751 Hex("block_id", req.GetBlockId()). 752 Msg("Successfully got transaction results from any node") 753 return resp, nil 754 } 755 if status.Code(err) == codes.NotFound { 756 return nil, err 757 } 758 errs = multierror.Append(errs, err) 759 } 760 761 // log the errors 762 return nil, errs.ErrorOrNil() 763 } 764 765 func (b *backendTransactions) tryGetTransactionResultsByBlockID( 766 ctx context.Context, 767 execNode *flow.Identity, 768 req *execproto.GetTransactionsByBlockIDRequest, 769 ) (*execproto.GetTransactionResultsResponse, error) { 770 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 771 if err != nil { 772 return nil, err 773 } 774 defer closer.Close() 775 776 resp, err := execRPCClient.GetTransactionResultsByBlockID(ctx, req) 777 if err != nil { 778 if status.Code(err) == codes.Unavailable { 779 b.connFactory.InvalidateExecutionAPIClient(execNode.Address) 780 } 781 return nil, err 782 } 783 return resp, err 784 } 785 786 func (b *backendTransactions) getTransactionResultByIndexFromAnyExeNode( 787 ctx context.Context, 788 execNodes flow.IdentityList, 789 req *execproto.GetTransactionByIndexRequest, 790 ) (*execproto.GetTransactionResultResponse, error) { 791 var errs *multierror.Error 792 logAnyError := func() { 793 errToReturn := errs.ErrorOrNil() 794 if errToReturn != nil { 795 b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes") 796 } 797 } 798 defer logAnyError() 799 800 if len(execNodes) == 0 { 801 return nil, errors.New("zero execution nodes provided") 802 } 803 804 // try to execute the script on one of the execution nodes 805 for _, execNode := range execNodes { 806 resp, err := b.tryGetTransactionResultByIndex(ctx, execNode, req) 807 if err == nil { 808 b.log.Debug(). 809 Str("execution_node", execNode.String()). 810 Hex("block_id", req.GetBlockId()). 811 Uint32("index", req.GetIndex()). 812 Msg("Successfully got transaction results from any node") 813 return resp, nil 814 } 815 if status.Code(err) == codes.NotFound { 816 return nil, err 817 } 818 errs = multierror.Append(errs, err) 819 } 820 821 return nil, errs.ErrorOrNil() 822 } 823 824 func (b *backendTransactions) tryGetTransactionResultByIndex( 825 ctx context.Context, 826 execNode *flow.Identity, 827 req *execproto.GetTransactionByIndexRequest, 828 ) (*execproto.GetTransactionResultResponse, error) { 829 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 830 if err != nil { 831 return nil, err 832 } 833 defer closer.Close() 834 835 resp, err := execRPCClient.GetTransactionResultByIndex(ctx, req) 836 if err != nil { 837 if status.Code(err) == codes.Unavailable { 838 b.connFactory.InvalidateExecutionAPIClient(execNode.Address) 839 } 840 return nil, err 841 } 842 return resp, err 843 }