github.com/onflow/flow-go@v0.33.17/engine/access/rpc/backend/backend_transactions.go (about) 1 package backend 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 lru "github.com/hashicorp/golang-lru/v2" 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/onflow/flow-go/access" 18 "github.com/onflow/flow-go/engine/access/rpc/connection" 19 "github.com/onflow/flow-go/engine/common/rpc" 20 "github.com/onflow/flow-go/engine/common/rpc/convert" 21 "github.com/onflow/flow-go/model/flow" 22 "github.com/onflow/flow-go/module" 23 "github.com/onflow/flow-go/module/irrecoverable" 24 "github.com/onflow/flow-go/state" 25 "github.com/onflow/flow-go/storage" 26 ) 27 28 type backendTransactions struct { 29 TransactionsLocalDataProvider 30 staticCollectionRPC accessproto.AccessAPIClient // rpc client tied to a fixed collection node 31 transactions storage.Transactions 32 executionReceipts storage.ExecutionReceipts 33 chainID flow.ChainID 34 transactionMetrics module.TransactionMetrics 35 transactionValidator *access.TransactionValidator 36 retry *Retry 37 connFactory connection.ConnectionFactory 38 39 previousAccessNodes []accessproto.AccessAPIClient 40 log zerolog.Logger 41 nodeCommunicator Communicator 42 txResultCache *lru.Cache[flow.Identifier, *access.TransactionResult] 43 txErrorMessagesCache *lru.Cache[flow.Identifier, string] // cache for transactions error messages, indexed by hash(block_id, tx_id). 44 txResultQueryMode IndexQueryMode 45 46 systemTxID flow.Identifier 47 systemTx *flow.TransactionBody 48 } 49 50 var _ TransactionErrorMessage = (*backendTransactions)(nil) 51 52 // SendTransaction forwards the transaction to the collection node 53 func (b *backendTransactions) SendTransaction( 54 ctx context.Context, 55 tx *flow.TransactionBody, 56 ) error { 57 now := time.Now().UTC() 58 59 err := b.transactionValidator.Validate(tx) 60 if err != nil { 61 return status.Errorf(codes.InvalidArgument, "invalid transaction: %s", err.Error()) 62 } 63 64 // send the transaction to the collection node if valid 65 err = b.trySendTransaction(ctx, tx) 66 if err != nil { 67 b.transactionMetrics.TransactionSubmissionFailed() 68 return rpc.ConvertError(err, "failed to send transaction to a collection node", codes.Internal) 69 } 70 71 b.transactionMetrics.TransactionReceived(tx.ID(), now) 72 73 // store the transaction locally 74 err = b.transactions.Store(tx) 75 if err != nil { 76 return status.Errorf(codes.Internal, "failed to store transaction: %v", err) 77 } 78 79 if b.retry.IsActive() { 80 go b.registerTransactionForRetry(tx) 81 } 82 83 return nil 84 } 85 86 // trySendTransaction tries to transaction to a collection node 87 func (b *backendTransactions) trySendTransaction(ctx context.Context, tx *flow.TransactionBody) error { 88 // if a collection node rpc client was provided at startup, just use that 89 if b.staticCollectionRPC != nil { 90 return b.grpcTxSend(ctx, b.staticCollectionRPC, tx) 91 } 92 93 // otherwise choose all collection nodes to try 94 collNodes, err := b.chooseCollectionNodes(tx.ID()) 95 if err != nil { 96 return fmt.Errorf("failed to determine collection node for tx %x: %w", tx, err) 97 } 98 99 var sendError error 100 logAnyError := func() { 101 if sendError != nil { 102 b.log.Info().Err(err).Msg("failed to send transactions to collector nodes") 103 } 104 } 105 defer logAnyError() 106 107 // try sending the transaction to one of the chosen collection nodes 108 sendError = b.nodeCommunicator.CallAvailableNode( 109 collNodes, 110 func(node *flow.Identity) error { 111 err = b.sendTransactionToCollector(ctx, tx, node.Address) 112 if err != nil { 113 return err 114 } 115 return nil 116 }, 117 nil, 118 ) 119 120 return sendError 121 } 122 123 // chooseCollectionNodes finds a random subset of size sampleSize of collection node addresses from the 124 // collection node cluster responsible for the given tx 125 func (b *backendTransactions) chooseCollectionNodes(txID flow.Identifier) (flow.IdentityList, error) { 126 127 // retrieve the set of collector clusters 128 clusters, err := b.state.Final().Epochs().Current().Clustering() 129 if err != nil { 130 return nil, fmt.Errorf("could not cluster collection nodes: %w", err) 131 } 132 133 // get the cluster responsible for the transaction 134 targetNodes, ok := clusters.ByTxID(txID) 135 if !ok { 136 return nil, fmt.Errorf("could not get local cluster by txID: %x", txID) 137 } 138 139 return targetNodes, nil 140 } 141 142 // sendTransactionToCollection sends the transaction to the given collection node via grpc 143 func (b *backendTransactions) sendTransactionToCollector( 144 ctx context.Context, 145 tx *flow.TransactionBody, 146 collectionNodeAddr string, 147 ) error { 148 collectionRPC, closer, err := b.connFactory.GetAccessAPIClient(collectionNodeAddr, nil) 149 if err != nil { 150 return fmt.Errorf("failed to connect to collection node at %s: %w", collectionNodeAddr, err) 151 } 152 defer closer.Close() 153 154 err = b.grpcTxSend(ctx, collectionRPC, tx) 155 if err != nil { 156 return fmt.Errorf("failed to send transaction to collection node at %s: %w", collectionNodeAddr, err) 157 } 158 return nil 159 } 160 161 func (b *backendTransactions) grpcTxSend(ctx context.Context, client accessproto.AccessAPIClient, tx *flow.TransactionBody) error { 162 colReq := &accessproto.SendTransactionRequest{ 163 Transaction: convert.TransactionToMessage(*tx), 164 } 165 166 clientDeadline := time.Now().Add(time.Duration(2) * time.Second) 167 ctx, cancel := context.WithDeadline(ctx, clientDeadline) 168 defer cancel() 169 170 _, err := client.SendTransaction(ctx, colReq) 171 return err 172 } 173 174 // SendRawTransaction sends a raw transaction to the collection node 175 func (b *backendTransactions) SendRawTransaction( 176 ctx context.Context, 177 tx *flow.TransactionBody, 178 ) error { 179 // send the transaction to the collection node 180 return b.trySendTransaction(ctx, tx) 181 } 182 183 func (b *backendTransactions) GetTransaction(ctx context.Context, txID flow.Identifier) (*flow.TransactionBody, error) { 184 // look up transaction from storage 185 tx, err := b.transactions.ByID(txID) 186 txErr := rpc.ConvertStorageError(err) 187 188 if txErr != nil { 189 if status.Code(txErr) == codes.NotFound { 190 return b.getHistoricalTransaction(ctx, txID) 191 } 192 // Other Error trying to retrieve the transaction, return with err 193 return nil, txErr 194 } 195 196 return tx, nil 197 } 198 199 func (b *backendTransactions) GetTransactionsByBlockID( 200 _ context.Context, 201 blockID flow.Identifier, 202 ) ([]*flow.TransactionBody, error) { 203 var transactions []*flow.TransactionBody 204 205 // TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID 206 block, err := b.blocks.ByID(blockID) 207 if err != nil { 208 return nil, rpc.ConvertStorageError(err) 209 } 210 211 for _, guarantee := range block.Payload.Guarantees { 212 collection, err := b.collections.ByID(guarantee.CollectionID) 213 if err != nil { 214 return nil, rpc.ConvertStorageError(err) 215 } 216 217 transactions = append(transactions, collection.Transactions...) 218 } 219 220 transactions = append(transactions, b.systemTx) 221 222 return transactions, nil 223 } 224 225 func (b *backendTransactions) GetTransactionResult( 226 ctx context.Context, 227 txID flow.Identifier, 228 blockID flow.Identifier, 229 collectionID flow.Identifier, 230 requiredEventEncodingVersion entities.EventEncodingVersion, 231 ) (*access.TransactionResult, error) { 232 // look up transaction from storage 233 start := time.Now() 234 235 tx, err := b.transactions.ByID(txID) 236 if err != nil { 237 txErr := rpc.ConvertStorageError(err) 238 239 if status.Code(txErr) != codes.NotFound { 240 return nil, txErr 241 } 242 243 // Tx not found. If we have historical Sporks setup, lets look through those as well 244 if b.txResultCache != nil { 245 val, ok := b.txResultCache.Get(txID) 246 if ok { 247 return val, nil 248 } 249 } 250 historicalTxResult, err := b.getHistoricalTransactionResult(ctx, txID) 251 if err != nil { 252 // if tx not found in old access nodes either, then assume that the tx was submitted to a different AN 253 // and return status as unknown 254 txStatus := flow.TransactionStatusUnknown 255 result := &access.TransactionResult{ 256 Status: txStatus, 257 StatusCode: uint(txStatus), 258 } 259 if b.txResultCache != nil { 260 b.txResultCache.Add(txID, result) 261 } 262 return result, nil 263 } 264 265 if b.txResultCache != nil { 266 b.txResultCache.Add(txID, historicalTxResult) 267 } 268 return historicalTxResult, nil 269 } 270 271 block, err := b.retrieveBlock(blockID, collectionID, txID) 272 // an error occurred looking up the block or the requested block or collection was not found. 273 // If looking up the block based solely on the txID returns not found, then no error is 274 // returned since the block may not be finalized yet. 275 if err != nil { 276 return nil, rpc.ConvertStorageError(err) 277 } 278 279 var blockHeight uint64 280 var txResult *access.TransactionResult 281 // access node may not have the block if it hasn't yet been finalized, hence block can be nil at this point 282 if block != nil { 283 txResult, err = b.lookupTransactionResult(ctx, txID, block, requiredEventEncodingVersion) 284 if err != nil { 285 return nil, rpc.ConvertError(err, "failed to retrieve result", codes.Internal) 286 } 287 288 // an additional check to ensure the correctness of the collection ID. 289 expectedCollectionID, err := b.lookupCollectionIDInBlock(block, txID) 290 if err != nil { 291 // if the collection has not been indexed yet, the lookup will return a not found error. 292 // if the request included a blockID or collectionID in its the search criteria, not found 293 // should result in an error because it's not possible to guarantee that the result found 294 // is the correct one. 295 if blockID != flow.ZeroID || collectionID != flow.ZeroID { 296 return nil, rpc.ConvertStorageError(err) 297 } 298 } 299 300 if collectionID == flow.ZeroID { 301 collectionID = expectedCollectionID 302 } else if collectionID != expectedCollectionID { 303 return nil, status.Error(codes.InvalidArgument, "transaction not found in provided collection") 304 } 305 306 blockID = block.ID() 307 blockHeight = block.Header.Height 308 } 309 310 // If there is still no transaction result, provide one based on available information. 311 if txResult == nil { 312 var txStatus flow.TransactionStatus 313 // Derive the status of the transaction. 314 if block == nil { 315 txStatus, err = b.deriveUnknownTransactionStatus(tx.ReferenceBlockID) 316 } else { 317 txStatus, err = b.deriveTransactionStatus(blockID, blockHeight, false) 318 } 319 320 if err != nil { 321 if !errors.Is(err, state.ErrUnknownSnapshotReference) { 322 irrecoverable.Throw(ctx, err) 323 } 324 return nil, rpc.ConvertStorageError(err) 325 } 326 327 txResult = &access.TransactionResult{ 328 BlockID: blockID, 329 BlockHeight: blockHeight, 330 TransactionID: txID, 331 Status: txStatus, 332 CollectionID: collectionID, 333 } 334 } else { 335 txResult.CollectionID = collectionID 336 } 337 338 b.transactionMetrics.TransactionResultFetched(time.Since(start), len(tx.Script)) 339 340 return txResult, nil 341 } 342 343 // retrieveBlock function returns a block based on the input argument. The block ID lookup has the highest priority, 344 // followed by the collection ID lookup. If both are missing, the default lookup by transaction ID is performed. 345 func (b *backendTransactions) retrieveBlock( 346 // the requested block or collection was not found. If looking up the block based solely on the txID returns 347 // not found, then no error is returned. 348 blockID flow.Identifier, 349 collectionID flow.Identifier, 350 txID flow.Identifier, 351 ) (*flow.Block, error) { 352 if blockID != flow.ZeroID { 353 return b.blocks.ByID(blockID) 354 } 355 356 if collectionID != flow.ZeroID { 357 return b.blocks.ByCollectionID(collectionID) 358 } 359 360 // find the block for the transaction 361 block, err := b.lookupBlock(txID) 362 363 if err != nil && !errors.Is(err, storage.ErrNotFound) { 364 return nil, err 365 } 366 367 return block, nil 368 } 369 370 func (b *backendTransactions) GetTransactionResultsByBlockID( 371 ctx context.Context, 372 blockID flow.Identifier, 373 requiredEventEncodingVersion entities.EventEncodingVersion, 374 ) ([]*access.TransactionResult, error) { 375 // TODO: consider using storage.Index.ByBlockID, the index contains collection id and seals ID 376 block, err := b.blocks.ByID(blockID) 377 if err != nil { 378 return nil, rpc.ConvertStorageError(err) 379 } 380 381 switch b.txResultQueryMode { 382 case IndexQueryModeExecutionNodesOnly: 383 return b.getTransactionResultsByBlockIDFromExecutionNode(ctx, block, requiredEventEncodingVersion) 384 case IndexQueryModeLocalOnly: 385 return b.GetTransactionResultsByBlockIDFromStorage(ctx, block, requiredEventEncodingVersion) 386 case IndexQueryModeFailover: 387 results, err := b.GetTransactionResultsByBlockIDFromStorage(ctx, block, requiredEventEncodingVersion) 388 if err == nil { 389 return results, nil 390 } 391 392 // If any error occurs with local storage - request transaction result from EN 393 return b.getTransactionResultsByBlockIDFromExecutionNode(ctx, block, requiredEventEncodingVersion) 394 default: 395 return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode) 396 } 397 } 398 399 func (b *backendTransactions) getTransactionResultsByBlockIDFromExecutionNode( 400 ctx context.Context, 401 block *flow.Block, 402 requiredEventEncodingVersion entities.EventEncodingVersion, 403 ) ([]*access.TransactionResult, error) { 404 blockID := block.ID() 405 req := &execproto.GetTransactionsByBlockIDRequest{ 406 BlockId: blockID[:], 407 } 408 409 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 410 if err != nil { 411 if IsInsufficientExecutionReceipts(err) { 412 return nil, status.Errorf(codes.NotFound, err.Error()) 413 } 414 return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal) 415 } 416 417 resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req) 418 if err != nil { 419 return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal) 420 } 421 422 results := make([]*access.TransactionResult, 0, len(resp.TransactionResults)) 423 i := 0 424 errInsufficientResults := status.Errorf( 425 codes.Internal, 426 "number of transaction results returned by execution node is less than the number of transactions in the block", 427 ) 428 429 for _, guarantee := range block.Payload.Guarantees { 430 collection, err := b.collections.LightByID(guarantee.CollectionID) 431 if err != nil { 432 return nil, rpc.ConvertStorageError(err) 433 } 434 435 for _, txID := range collection.Transactions { 436 // bounds check. this means the EN returned fewer transaction results than the transactions in the block 437 if i >= len(resp.TransactionResults) { 438 return nil, errInsufficientResults 439 } 440 txResult := resp.TransactionResults[i] 441 442 // tx body is irrelevant to status if it's in an executed block 443 txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true) 444 if err != nil { 445 if !errors.Is(err, state.ErrUnknownSnapshotReference) { 446 irrecoverable.Throw(ctx, err) 447 } 448 return nil, rpc.ConvertStorageError(err) 449 } 450 events, err := convert.MessagesToEventsWithEncodingConversion(txResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion) 451 if err != nil { 452 return nil, status.Errorf(codes.Internal, 453 "failed to convert events to message in txID %x: %v", txID, err) 454 } 455 456 results = append(results, &access.TransactionResult{ 457 Status: txStatus, 458 StatusCode: uint(txResult.GetStatusCode()), 459 Events: events, 460 ErrorMessage: txResult.GetErrorMessage(), 461 BlockID: blockID, 462 TransactionID: txID, 463 CollectionID: guarantee.CollectionID, 464 BlockHeight: block.Header.Height, 465 }) 466 467 i++ 468 } 469 } 470 471 // after iterating through all transactions in each collection, i equals the total number of 472 // user transactions in the block 473 txCount := i 474 475 sporkRootBlockHeight, err := b.state.Params().SporkRootBlockHeight() 476 if err != nil { 477 return nil, status.Errorf(codes.Internal, "failed to retrieve root block: %v", err) 478 } 479 480 // root block has no system transaction result 481 if block.Header.Height > sporkRootBlockHeight { 482 // system chunk transaction 483 484 // resp.TransactionResults includes the system tx result, so there should be exactly one 485 // more result than txCount 486 if txCount != len(resp.TransactionResults)-1 { 487 if txCount >= len(resp.TransactionResults) { 488 return nil, errInsufficientResults 489 } 490 // otherwise there are extra results 491 // TODO(bft): slashable offense 492 return nil, status.Errorf(codes.Internal, "number of transaction results returned by execution node is more than the number of transactions in the block") 493 } 494 495 systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1] 496 systemTxStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true) 497 if err != nil { 498 if !errors.Is(err, state.ErrUnknownSnapshotReference) { 499 irrecoverable.Throw(ctx, err) 500 } 501 return nil, rpc.ConvertStorageError(err) 502 } 503 504 events, err := convert.MessagesToEventsWithEncodingConversion(systemTxResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion) 505 if err != nil { 506 return nil, rpc.ConvertError(err, "failed to convert events from system tx result", codes.Internal) 507 } 508 509 results = append(results, &access.TransactionResult{ 510 Status: systemTxStatus, 511 StatusCode: uint(systemTxResult.GetStatusCode()), 512 Events: events, 513 ErrorMessage: systemTxResult.GetErrorMessage(), 514 BlockID: blockID, 515 TransactionID: b.systemTxID, 516 BlockHeight: block.Header.Height, 517 }) 518 } 519 return results, nil 520 } 521 522 // GetTransactionResultByIndex returns transactions Results for an index in a block that is executed, 523 // pending or finalized transactions return errors 524 func (b *backendTransactions) GetTransactionResultByIndex( 525 ctx context.Context, 526 blockID flow.Identifier, 527 index uint32, 528 requiredEventEncodingVersion entities.EventEncodingVersion, 529 ) (*access.TransactionResult, error) { 530 // TODO: https://github.com/onflow/flow-go/issues/2175 so caching doesn't cause a circular dependency 531 block, err := b.blocks.ByID(blockID) 532 if err != nil { 533 return nil, rpc.ConvertStorageError(err) 534 } 535 536 switch b.txResultQueryMode { 537 case IndexQueryModeExecutionNodesOnly: 538 return b.getTransactionResultByIndexFromExecutionNode(ctx, block, index, requiredEventEncodingVersion) 539 case IndexQueryModeLocalOnly: 540 return b.GetTransactionResultByIndexFromStorage(ctx, block, index, requiredEventEncodingVersion) 541 case IndexQueryModeFailover: 542 result, err := b.GetTransactionResultByIndexFromStorage(ctx, block, index, requiredEventEncodingVersion) 543 if err == nil { 544 return result, nil 545 } 546 547 // If any error occurs with local storage - request transaction result from EN 548 return b.getTransactionResultByIndexFromExecutionNode(ctx, block, index, requiredEventEncodingVersion) 549 default: 550 return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode) 551 } 552 } 553 554 func (b *backendTransactions) getTransactionResultByIndexFromExecutionNode( 555 ctx context.Context, 556 block *flow.Block, 557 index uint32, 558 requiredEventEncodingVersion entities.EventEncodingVersion, 559 ) (*access.TransactionResult, error) { 560 blockID := block.ID() 561 // create request and forward to EN 562 req := &execproto.GetTransactionByIndexRequest{ 563 BlockId: blockID[:], 564 Index: index, 565 } 566 567 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 568 if err != nil { 569 if IsInsufficientExecutionReceipts(err) { 570 return nil, status.Errorf(codes.NotFound, err.Error()) 571 } 572 return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal) 573 } 574 575 resp, err := b.getTransactionResultByIndexFromAnyExeNode(ctx, execNodes, req) 576 if err != nil { 577 return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal) 578 } 579 580 // tx body is irrelevant to status if it's in an executed block 581 txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true) 582 if err != nil { 583 if !errors.Is(err, state.ErrUnknownSnapshotReference) { 584 irrecoverable.Throw(ctx, err) 585 } 586 return nil, rpc.ConvertStorageError(err) 587 } 588 589 events, err := convert.MessagesToEventsWithEncodingConversion(resp.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion) 590 if err != nil { 591 return nil, status.Errorf(codes.Internal, "failed to convert events in blockID %x: %v", blockID, err) 592 } 593 594 // convert to response, cache and return 595 return &access.TransactionResult{ 596 Status: txStatus, 597 StatusCode: uint(resp.GetStatusCode()), 598 Events: events, 599 ErrorMessage: resp.GetErrorMessage(), 600 BlockID: blockID, 601 BlockHeight: block.Header.Height, 602 }, nil 603 } 604 605 // GetSystemTransaction returns system transaction 606 func (b *backendTransactions) GetSystemTransaction(ctx context.Context, _ flow.Identifier) (*flow.TransactionBody, error) { 607 return b.systemTx, nil 608 } 609 610 // GetSystemTransactionResult returns system transaction result 611 func (b *backendTransactions) GetSystemTransactionResult(ctx context.Context, blockID flow.Identifier, requiredEventEncodingVersion entities.EventEncodingVersion) (*access.TransactionResult, error) { 612 block, err := b.blocks.ByID(blockID) 613 if err != nil { 614 return nil, rpc.ConvertStorageError(err) 615 } 616 617 req := &execproto.GetTransactionsByBlockIDRequest{ 618 BlockId: blockID[:], 619 } 620 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 621 if err != nil { 622 if IsInsufficientExecutionReceipts(err) { 623 return nil, status.Errorf(codes.NotFound, err.Error()) 624 } 625 return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal) 626 } 627 628 resp, err := b.getTransactionResultsByBlockIDFromAnyExeNode(ctx, execNodes, req) 629 if err != nil { 630 return nil, rpc.ConvertError(err, "failed to retrieve result from execution node", codes.Internal) 631 } 632 633 systemTxResult := resp.TransactionResults[len(resp.TransactionResults)-1] 634 systemTxStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true) 635 if err != nil { 636 return nil, rpc.ConvertStorageError(err) 637 } 638 639 events, err := convert.MessagesToEventsWithEncodingConversion(systemTxResult.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion) 640 if err != nil { 641 return nil, rpc.ConvertError(err, "failed to convert events from system tx result", codes.Internal) 642 } 643 644 return &access.TransactionResult{ 645 Status: systemTxStatus, 646 StatusCode: uint(systemTxResult.GetStatusCode()), 647 Events: events, 648 ErrorMessage: systemTxResult.GetErrorMessage(), 649 BlockID: blockID, 650 TransactionID: b.systemTxID, 651 BlockHeight: block.Header.Height, 652 }, nil 653 } 654 655 // Error returns: 656 // - `storage.ErrNotFound` - collection referenced by transaction or block by a collection has not been found. 657 // - all other errors are unexpected and potentially symptoms of internal implementation bugs or state corruption (fatal). 658 func (b *backendTransactions) lookupBlock(txID flow.Identifier) (*flow.Block, error) { 659 collection, err := b.collections.LightByTransactionID(txID) 660 if err != nil { 661 return nil, err 662 } 663 664 block, err := b.blocks.ByCollectionID(collection.ID()) 665 if err != nil { 666 return nil, err 667 } 668 669 return block, nil 670 } 671 672 func (b *backendTransactions) lookupTransactionResult( 673 ctx context.Context, 674 txID flow.Identifier, 675 block *flow.Block, 676 requiredEventEncodingVersion entities.EventEncodingVersion, 677 ) (*access.TransactionResult, error) { 678 var txResult *access.TransactionResult 679 var err error 680 switch b.txResultQueryMode { 681 case IndexQueryModeExecutionNodesOnly: 682 txResult, err = b.getTransactionResultFromExecutionNode(ctx, block, txID, requiredEventEncodingVersion) 683 case IndexQueryModeLocalOnly: 684 txResult, err = b.GetTransactionResultFromStorage(ctx, block, txID, requiredEventEncodingVersion) 685 case IndexQueryModeFailover: 686 txResult, err = b.GetTransactionResultFromStorage(ctx, block, txID, requiredEventEncodingVersion) 687 if err != nil { 688 // If any error occurs with local storage - request transaction result from EN 689 txResult, err = b.getTransactionResultFromExecutionNode(ctx, block, txID, requiredEventEncodingVersion) 690 } 691 default: 692 return nil, status.Errorf(codes.Internal, "unknown transaction result query mode: %v", b.txResultQueryMode) 693 } 694 695 if err != nil { 696 // if either the storage or execution node reported no results or there were not enough execution results 697 if status.Code(err) == codes.NotFound { 698 // No result yet, indicate that it has not been executed 699 return nil, nil 700 } 701 // Other Error trying to retrieve the result, return with err 702 return nil, err 703 } 704 705 // considered executed as long as some result is returned, even if it's an error message 706 return txResult, nil 707 } 708 709 func (b *backendTransactions) getHistoricalTransaction( 710 ctx context.Context, 711 txID flow.Identifier, 712 ) (*flow.TransactionBody, error) { 713 for _, historicalNode := range b.previousAccessNodes { 714 txResp, err := historicalNode.GetTransaction(ctx, &accessproto.GetTransactionRequest{Id: txID[:]}) 715 if err == nil { 716 tx, err := convert.MessageToTransaction(txResp.Transaction, b.chainID.Chain()) 717 if err != nil { 718 return nil, status.Errorf(codes.Internal, "could not convert transaction: %v", err) 719 } 720 721 // Found on a historical node. Report 722 return &tx, nil 723 } 724 // Otherwise, if not found, just continue 725 if status.Code(err) == codes.NotFound { 726 continue 727 } 728 // TODO should we do something if the error isn't not found? 729 } 730 return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID) 731 } 732 733 func (b *backendTransactions) getHistoricalTransactionResult( 734 ctx context.Context, 735 txID flow.Identifier, 736 ) (*access.TransactionResult, error) { 737 for _, historicalNode := range b.previousAccessNodes { 738 result, err := historicalNode.GetTransactionResult(ctx, &accessproto.GetTransactionRequest{Id: txID[:]}) 739 if err == nil { 740 // Found on a historical node. Report 741 if result.GetStatus() == entities.TransactionStatus_UNKNOWN { 742 // We've moved to returning Status UNKNOWN instead of an error with the NotFound status, 743 // Therefore we should continue and look at the next access node for answers. 744 continue 745 } 746 747 if result.GetStatus() == entities.TransactionStatus_PENDING { 748 // This is on a historical node. No transactions from it will ever be 749 // executed, therefore we should consider this expired 750 result.Status = entities.TransactionStatus_EXPIRED 751 } 752 753 return access.MessageToTransactionResult(result), nil 754 } 755 // Otherwise, if not found, just continue 756 if status.Code(err) == codes.NotFound { 757 continue 758 } 759 // TODO should we do something if the error isn't not found? 760 } 761 return nil, status.Errorf(codes.NotFound, "no known transaction with ID %s", txID) 762 } 763 764 func (b *backendTransactions) registerTransactionForRetry(tx *flow.TransactionBody) { 765 referenceBlock, err := b.state.AtBlockID(tx.ReferenceBlockID).Head() 766 if err != nil { 767 return 768 } 769 770 b.retry.RegisterTransaction(referenceBlock.Height, tx) 771 } 772 773 func (b *backendTransactions) getTransactionResultFromExecutionNode( 774 ctx context.Context, 775 block *flow.Block, 776 transactionID flow.Identifier, 777 requiredEventEncodingVersion entities.EventEncodingVersion, 778 ) (*access.TransactionResult, error) { 779 blockID := block.ID() 780 // create an execution API request for events at blockID and transactionID 781 req := &execproto.GetTransactionResultRequest{ 782 BlockId: blockID[:], 783 TransactionId: transactionID[:], 784 } 785 786 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 787 if err != nil { 788 // if no execution receipt were found, return a NotFound GRPC error 789 if IsInsufficientExecutionReceipts(err) { 790 return nil, status.Errorf(codes.NotFound, err.Error()) 791 } 792 return nil, err 793 } 794 795 resp, err := b.getTransactionResultFromAnyExeNode(ctx, execNodes, req) 796 if err != nil { 797 return nil, err 798 } 799 800 // tx body is irrelevant to status if it's in an executed block 801 txStatus, err := b.deriveTransactionStatus(blockID, block.Header.Height, true) 802 if err != nil { 803 if !errors.Is(err, state.ErrUnknownSnapshotReference) { 804 irrecoverable.Throw(ctx, err) 805 } 806 return nil, rpc.ConvertStorageError(err) 807 } 808 809 events, err := convert.MessagesToEventsWithEncodingConversion(resp.GetEvents(), resp.GetEventEncodingVersion(), requiredEventEncodingVersion) 810 if err != nil { 811 return nil, rpc.ConvertError(err, "failed to convert events to message", codes.Internal) 812 } 813 814 return &access.TransactionResult{ 815 TransactionID: transactionID, 816 Status: txStatus, 817 StatusCode: uint(resp.GetStatusCode()), 818 Events: events, 819 ErrorMessage: resp.GetErrorMessage(), 820 BlockID: blockID, 821 BlockHeight: block.Header.Height, 822 }, nil 823 } 824 825 // ATTENTION: might be a source of problems in future. We run this code on finalization gorotuine, 826 // potentially lagging finalization events if operations take long time. 827 // We might need to move this logic on dedicated goroutine and provide a way to skip finalization events if they are delivered 828 // too often for this engine. An example of similar approach - https://github.com/onflow/flow-go/blob/10b0fcbf7e2031674c00f3cdd280f27bd1b16c47/engine/common/follower/compliance_engine.go#L201.. 829 // No errors expected during normal operations. 830 func (b *backendTransactions) ProcessFinalizedBlockHeight(height uint64) error { 831 return b.retry.Retry(height) 832 } 833 834 func (b *backendTransactions) getTransactionResultFromAnyExeNode( 835 ctx context.Context, 836 execNodes flow.IdentityList, 837 req *execproto.GetTransactionResultRequest, 838 ) (*execproto.GetTransactionResultResponse, error) { 839 var errToReturn error 840 841 defer func() { 842 if errToReturn != nil { 843 b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes") 844 } 845 }() 846 847 var resp *execproto.GetTransactionResultResponse 848 errToReturn = b.nodeCommunicator.CallAvailableNode( 849 execNodes, 850 func(node *flow.Identity) error { 851 var err error 852 resp, err = b.tryGetTransactionResult(ctx, node, req) 853 if err == nil { 854 b.log.Debug(). 855 Str("execution_node", node.String()). 856 Hex("block_id", req.GetBlockId()). 857 Hex("transaction_id", req.GetTransactionId()). 858 Msg("Successfully got transaction results from any node") 859 return nil 860 } 861 return err 862 }, 863 nil, 864 ) 865 866 return resp, errToReturn 867 } 868 869 func (b *backendTransactions) tryGetTransactionResult( 870 ctx context.Context, 871 execNode *flow.Identity, 872 req *execproto.GetTransactionResultRequest, 873 ) (*execproto.GetTransactionResultResponse, error) { 874 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 875 if err != nil { 876 return nil, err 877 } 878 defer closer.Close() 879 880 resp, err := execRPCClient.GetTransactionResult(ctx, req) 881 if err != nil { 882 return nil, err 883 } 884 885 return resp, nil 886 } 887 888 func (b *backendTransactions) getTransactionResultsByBlockIDFromAnyExeNode( 889 ctx context.Context, 890 execNodes flow.IdentityList, 891 req *execproto.GetTransactionsByBlockIDRequest, 892 ) (*execproto.GetTransactionResultsResponse, error) { 893 var errToReturn error 894 895 defer func() { 896 // log the errors 897 if errToReturn != nil { 898 b.log.Err(errToReturn).Msg("failed to get transaction results from execution nodes") 899 } 900 }() 901 902 // if we were passed 0 execution nodes add a specific error 903 if len(execNodes) == 0 { 904 return nil, errors.New("zero execution nodes") 905 } 906 907 var resp *execproto.GetTransactionResultsResponse 908 errToReturn = b.nodeCommunicator.CallAvailableNode( 909 execNodes, 910 func(node *flow.Identity) error { 911 var err error 912 resp, err = b.tryGetTransactionResultsByBlockID(ctx, node, req) 913 if err == nil { 914 b.log.Debug(). 915 Str("execution_node", node.String()). 916 Hex("block_id", req.GetBlockId()). 917 Msg("Successfully got transaction results from any node") 918 return nil 919 } 920 return err 921 }, 922 nil, 923 ) 924 925 return resp, errToReturn 926 } 927 928 func (b *backendTransactions) tryGetTransactionResultsByBlockID( 929 ctx context.Context, 930 execNode *flow.Identity, 931 req *execproto.GetTransactionsByBlockIDRequest, 932 ) (*execproto.GetTransactionResultsResponse, error) { 933 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 934 if err != nil { 935 return nil, err 936 } 937 defer closer.Close() 938 939 resp, err := execRPCClient.GetTransactionResultsByBlockID(ctx, req) 940 if err != nil { 941 return nil, err 942 } 943 944 return resp, nil 945 } 946 947 func (b *backendTransactions) getTransactionResultByIndexFromAnyExeNode( 948 ctx context.Context, 949 execNodes flow.IdentityList, 950 req *execproto.GetTransactionByIndexRequest, 951 ) (*execproto.GetTransactionResultResponse, error) { 952 var errToReturn error 953 defer func() { 954 if errToReturn != nil { 955 b.log.Info().Err(errToReturn).Msg("failed to get transaction result from execution nodes") 956 } 957 }() 958 959 if len(execNodes) == 0 { 960 return nil, errors.New("zero execution nodes provided") 961 } 962 963 var resp *execproto.GetTransactionResultResponse 964 errToReturn = b.nodeCommunicator.CallAvailableNode( 965 execNodes, 966 func(node *flow.Identity) error { 967 var err error 968 resp, err = b.tryGetTransactionResultByIndex(ctx, node, req) 969 if err == nil { 970 b.log.Debug(). 971 Str("execution_node", node.String()). 972 Hex("block_id", req.GetBlockId()). 973 Uint32("index", req.GetIndex()). 974 Msg("Successfully got transaction results from any node") 975 return nil 976 } 977 return err 978 }, 979 nil, 980 ) 981 982 return resp, errToReturn 983 } 984 985 func (b *backendTransactions) tryGetTransactionResultByIndex( 986 ctx context.Context, 987 execNode *flow.Identity, 988 req *execproto.GetTransactionByIndexRequest, 989 ) (*execproto.GetTransactionResultResponse, error) { 990 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 991 if err != nil { 992 return nil, err 993 } 994 defer closer.Close() 995 996 resp, err := execRPCClient.GetTransactionResultByIndex(ctx, req) 997 if err != nil { 998 return nil, err 999 } 1000 1001 return resp, nil 1002 } 1003 1004 // LookupErrorMessageByTransactionID returns transaction error message for specified transaction. 1005 // If an error message for transaction can be found in the cache then it will be used to serve the request, otherwise 1006 // an RPC call will be made to the EN to fetch that error message, fetched value will be cached in the LRU cache. 1007 // Expected errors during normal operation: 1008 // - InsufficientExecutionReceipts - found insufficient receipts for given block ID. 1009 // - status.Error - remote GRPC call to EN has failed. 1010 func (b *backendTransactions) LookupErrorMessageByTransactionID( 1011 ctx context.Context, 1012 blockID flow.Identifier, 1013 transactionID flow.Identifier, 1014 ) (string, error) { 1015 var cacheKey flow.Identifier 1016 var value string 1017 1018 if b.txErrorMessagesCache != nil { 1019 cacheKey = flow.MakeIDFromFingerPrint(append(blockID[:], transactionID[:]...)) 1020 value, cached := b.txErrorMessagesCache.Get(cacheKey) 1021 if cached { 1022 return value, nil 1023 } 1024 } 1025 1026 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 1027 if err != nil { 1028 if IsInsufficientExecutionReceipts(err) { 1029 return "", status.Errorf(codes.NotFound, err.Error()) 1030 } 1031 return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) 1032 } 1033 req := &execproto.GetTransactionErrorMessageRequest{ 1034 BlockId: convert.IdentifierToMessage(blockID), 1035 TransactionId: convert.IdentifierToMessage(transactionID), 1036 } 1037 1038 resp, err := b.getTransactionErrorMessageFromAnyEN(ctx, execNodes, req) 1039 if err != nil { 1040 return "", fmt.Errorf("could not fetch error message from ENs: %w", err) 1041 } 1042 value = resp.ErrorMessage 1043 1044 if b.txErrorMessagesCache != nil { 1045 b.txErrorMessagesCache.Add(cacheKey, value) 1046 } 1047 1048 return value, nil 1049 } 1050 1051 // LookupErrorMessageByIndex returns transaction error message for specified transaction using its index. 1052 // If an error message for transaction can be found in cache then it will be used to serve the request, otherwise 1053 // an RPC call will be made to the EN to fetch that error message, fetched value will be cached in the LRU cache. 1054 // Expected errors during normal operation: 1055 // - status.Error[codes.NotFound] - transaction result for given block ID and tx index is not available. 1056 // - InsufficientExecutionReceipts - found insufficient receipts for given block ID. 1057 // - status.Error - remote GRPC call to EN has failed. 1058 func (b *backendTransactions) LookupErrorMessageByIndex( 1059 ctx context.Context, 1060 blockID flow.Identifier, 1061 height uint64, 1062 index uint32, 1063 ) (string, error) { 1064 txResult, err := b.txResultsIndex.ByBlockIDTransactionIndex(blockID, height, index) 1065 if err != nil { 1066 return "", rpc.ConvertStorageError(err) 1067 } 1068 1069 var cacheKey flow.Identifier 1070 var value string 1071 1072 if b.txErrorMessagesCache != nil { 1073 cacheKey = flow.MakeIDFromFingerPrint(append(blockID[:], txResult.TransactionID[:]...)) 1074 value, cached := b.txErrorMessagesCache.Get(cacheKey) 1075 if cached { 1076 return value, nil 1077 } 1078 } 1079 1080 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 1081 if err != nil { 1082 if IsInsufficientExecutionReceipts(err) { 1083 return "", status.Errorf(codes.NotFound, err.Error()) 1084 } 1085 return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) 1086 } 1087 req := &execproto.GetTransactionErrorMessageByIndexRequest{ 1088 BlockId: convert.IdentifierToMessage(blockID), 1089 Index: index, 1090 } 1091 1092 resp, err := b.getTransactionErrorMessageByIndexFromAnyEN(ctx, execNodes, req) 1093 if err != nil { 1094 return "", fmt.Errorf("could not fetch error message from ENs: %w", err) 1095 } 1096 value = resp.ErrorMessage 1097 1098 if b.txErrorMessagesCache != nil { 1099 b.txErrorMessagesCache.Add(cacheKey, value) 1100 } 1101 1102 return value, nil 1103 } 1104 1105 // LookupErrorMessagesByBlockID returns all error messages for failed transactions by blockID. 1106 // An RPC call will be made to the EN to fetch missing errors messages, fetched value will be cached in the LRU cache. 1107 // Expected errors during normal operation: 1108 // - status.Error[codes.NotFound] - transaction results for given block ID are not available. 1109 // - InsufficientExecutionReceipts - found insufficient receipts for given block ID. 1110 // - status.Error - remote GRPC call to EN has failed. 1111 func (b *backendTransactions) LookupErrorMessagesByBlockID( 1112 ctx context.Context, 1113 blockID flow.Identifier, 1114 height uint64, 1115 ) (map[flow.Identifier]string, error) { 1116 txResults, err := b.txResultsIndex.ByBlockID(blockID, height) 1117 if err != nil { 1118 return nil, rpc.ConvertStorageError(err) 1119 } 1120 1121 results := make(map[flow.Identifier]string) 1122 1123 if b.txErrorMessagesCache != nil { 1124 needToFetch := false 1125 for _, txResult := range txResults { 1126 if txResult.Failed { 1127 cacheKey := flow.MakeIDFromFingerPrint(append(blockID[:], txResult.TransactionID[:]...)) 1128 if value, ok := b.txErrorMessagesCache.Get(cacheKey); ok { 1129 results[txResult.TransactionID] = value 1130 } else { 1131 needToFetch = true 1132 } 1133 } 1134 } 1135 1136 // all transactions were served from cache or there were no failed transactions 1137 if !needToFetch { 1138 return results, nil 1139 } 1140 } 1141 1142 execNodes, err := executionNodesForBlockID(ctx, blockID, b.executionReceipts, b.state, b.log) 1143 if err != nil { 1144 if IsInsufficientExecutionReceipts(err) { 1145 return nil, status.Errorf(codes.NotFound, err.Error()) 1146 } 1147 return nil, rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) 1148 } 1149 req := &execproto.GetTransactionErrorMessagesByBlockIDRequest{ 1150 BlockId: convert.IdentifierToMessage(blockID), 1151 } 1152 1153 resp, err := b.getTransactionErrorMessagesFromAnyEN(ctx, execNodes, req) 1154 if err != nil { 1155 return nil, fmt.Errorf("could not fetch error message from ENs: %w", err) 1156 } 1157 result := make(map[flow.Identifier]string, len(resp)) 1158 for _, value := range resp { 1159 if b.txErrorMessagesCache != nil { 1160 cacheKey := flow.MakeIDFromFingerPrint(append(req.BlockId, value.TransactionId...)) 1161 b.txErrorMessagesCache.Add(cacheKey, value.ErrorMessage) 1162 } 1163 result[convert.MessageToIdentifier(value.TransactionId)] = value.ErrorMessage 1164 } 1165 return result, nil 1166 } 1167 1168 // getTransactionErrorMessageFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned. 1169 // Expected errors during normal operation: 1170 // - status.Error - GRPC call failed, some of possible codes are: 1171 // - codes.NotFound - request cannot be served by EN because of absence of data. 1172 // - codes.Unavailable - remote node is not unavailable. 1173 func (b *backendTransactions) getTransactionErrorMessageFromAnyEN( 1174 ctx context.Context, 1175 execNodes flow.IdentityList, 1176 req *execproto.GetTransactionErrorMessageRequest, 1177 ) (*execproto.GetTransactionErrorMessageResponse, error) { 1178 // if we were passed 0 execution nodes add a specific error 1179 if len(execNodes) == 0 { 1180 return nil, errors.New("zero execution nodes") 1181 } 1182 1183 var resp *execproto.GetTransactionErrorMessageResponse 1184 errToReturn := b.nodeCommunicator.CallAvailableNode( 1185 execNodes, 1186 func(node *flow.Identity) error { 1187 var err error 1188 resp, err = b.tryGetTransactionErrorMessageFromEN(ctx, node, req) 1189 if err == nil { 1190 b.log.Debug(). 1191 Str("execution_node", node.String()). 1192 Hex("block_id", req.GetBlockId()). 1193 Hex("transaction_id", req.GetTransactionId()). 1194 Msg("Successfully got transaction error message from any node") 1195 return nil 1196 } 1197 return err 1198 }, 1199 nil, 1200 ) 1201 1202 // log the errors 1203 if errToReturn != nil { 1204 b.log.Err(errToReturn).Msg("failed to get transaction error message from execution nodes") 1205 return nil, errToReturn 1206 } 1207 1208 return resp, nil 1209 } 1210 1211 // getTransactionErrorMessageFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned. 1212 // Expected errors during normal operation: 1213 // - status.Error - GRPC call failed, some of possible codes are: 1214 // - codes.NotFound - request cannot be served by EN because of absence of data. 1215 // - codes.Unavailable - remote node is not unavailable. 1216 func (b *backendTransactions) getTransactionErrorMessageByIndexFromAnyEN( 1217 ctx context.Context, 1218 execNodes flow.IdentityList, 1219 req *execproto.GetTransactionErrorMessageByIndexRequest, 1220 ) (*execproto.GetTransactionErrorMessageResponse, error) { 1221 // if we were passed 0 execution nodes add a specific error 1222 if len(execNodes) == 0 { 1223 return nil, errors.New("zero execution nodes") 1224 } 1225 1226 var resp *execproto.GetTransactionErrorMessageResponse 1227 errToReturn := b.nodeCommunicator.CallAvailableNode( 1228 execNodes, 1229 func(node *flow.Identity) error { 1230 var err error 1231 resp, err = b.tryGetTransactionErrorMessageByIndexFromEN(ctx, node, req) 1232 if err == nil { 1233 b.log.Debug(). 1234 Str("execution_node", node.String()). 1235 Hex("block_id", req.GetBlockId()). 1236 Uint32("index", req.GetIndex()). 1237 Msg("Successfully got transaction error message by index from any node") 1238 return nil 1239 } 1240 return err 1241 }, 1242 nil, 1243 ) 1244 if errToReturn != nil { 1245 b.log.Err(errToReturn).Msg("failed to get transaction error message by index from execution nodes") 1246 return nil, errToReturn 1247 } 1248 1249 return resp, nil 1250 } 1251 1252 // getTransactionErrorMessagesFromAnyEN performs an RPC call using available nodes passed as argument. List of nodes must be non-empty otherwise an error will be returned. 1253 // Expected errors during normal operation: 1254 // - status.Error - GRPC call failed, some of possible codes are: 1255 // - codes.NotFound - request cannot be served by EN because of absence of data. 1256 // - codes.Unavailable - remote node is not unavailable. 1257 func (b *backendTransactions) getTransactionErrorMessagesFromAnyEN( 1258 ctx context.Context, 1259 execNodes flow.IdentityList, 1260 req *execproto.GetTransactionErrorMessagesByBlockIDRequest, 1261 ) ([]*execproto.GetTransactionErrorMessagesResponse_Result, error) { 1262 // if we were passed 0 execution nodes add a specific error 1263 if len(execNodes) == 0 { 1264 return nil, errors.New("zero execution nodes") 1265 } 1266 1267 var resp *execproto.GetTransactionErrorMessagesResponse 1268 errToReturn := b.nodeCommunicator.CallAvailableNode( 1269 execNodes, 1270 func(node *flow.Identity) error { 1271 var err error 1272 resp, err = b.tryGetTransactionErrorMessagesByBlockIDFromEN(ctx, node, req) 1273 if err == nil { 1274 b.log.Debug(). 1275 Str("execution_node", node.String()). 1276 Hex("block_id", req.GetBlockId()). 1277 Msg("Successfully got transaction error messages from any node") 1278 return nil 1279 } 1280 return err 1281 }, 1282 nil, 1283 ) 1284 1285 // log the errors 1286 if errToReturn != nil { 1287 b.log.Err(errToReturn).Msg("failed to get transaction error messages from execution nodes") 1288 return nil, errToReturn 1289 } 1290 1291 return resp.GetResults(), nil 1292 } 1293 1294 // Expected errors during normal operation: 1295 // - status.Error - GRPC call failed, some of possible codes are: 1296 // - codes.NotFound - request cannot be served by EN because of absence of data. 1297 // - codes.Unavailable - remote node is not unavailable. 1298 // 1299 // tryGetTransactionErrorMessageFromEN performs a grpc call to the specified execution node and returns response. 1300 func (b *backendTransactions) tryGetTransactionErrorMessageFromEN( 1301 ctx context.Context, 1302 execNode *flow.Identity, 1303 req *execproto.GetTransactionErrorMessageRequest, 1304 ) (*execproto.GetTransactionErrorMessageResponse, error) { 1305 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 1306 if err != nil { 1307 return nil, err 1308 } 1309 defer closer.Close() 1310 return execRPCClient.GetTransactionErrorMessage(ctx, req) 1311 } 1312 1313 // tryGetTransactionErrorMessageByIndexFromEN performs a grpc call to the specified execution node and returns response. 1314 // Expected errors during normal operation: 1315 // - status.Error - GRPC call failed, some of possible codes are: 1316 // - codes.NotFound - request cannot be served by EN because of absence of data. 1317 // - codes.Unavailable - remote node is not unavailable. 1318 func (b *backendTransactions) tryGetTransactionErrorMessageByIndexFromEN( 1319 ctx context.Context, 1320 execNode *flow.Identity, 1321 req *execproto.GetTransactionErrorMessageByIndexRequest, 1322 ) (*execproto.GetTransactionErrorMessageResponse, error) { 1323 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 1324 if err != nil { 1325 return nil, err 1326 } 1327 defer closer.Close() 1328 return execRPCClient.GetTransactionErrorMessageByIndex(ctx, req) 1329 } 1330 1331 // tryGetTransactionErrorMessagesByBlockIDFromEN performs a grpc call to the specified execution node and returns response. 1332 // Expected errors during normal operation: 1333 // - status.Error - GRPC call failed, some of possible codes are: 1334 // - codes.NotFound - request cannot be served by EN because of absence of data. 1335 // - codes.Unavailable - remote node is not unavailable. 1336 func (b *backendTransactions) tryGetTransactionErrorMessagesByBlockIDFromEN( 1337 ctx context.Context, 1338 execNode *flow.Identity, 1339 req *execproto.GetTransactionErrorMessagesByBlockIDRequest, 1340 ) (*execproto.GetTransactionErrorMessagesResponse, error) { 1341 execRPCClient, closer, err := b.connFactory.GetExecutionAPIClient(execNode.Address) 1342 if err != nil { 1343 return nil, err 1344 } 1345 defer closer.Close() 1346 return execRPCClient.GetTransactionErrorMessagesByBlockID(ctx, req) 1347 }