github.com/koko1123/flow-go-1@v0.29.6/engine/access/apiproxy/access_api_proxy.go (about) 1 package apiproxy 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "google.golang.org/grpc/connectivity" 10 11 "google.golang.org/grpc" 12 "google.golang.org/grpc/codes" 13 "google.golang.org/grpc/credentials" 14 "google.golang.org/grpc/status" 15 16 "github.com/onflow/flow/protobuf/go/flow/access" 17 "github.com/rs/zerolog" 18 19 "github.com/koko1123/flow-go-1/engine/access/rpc/backend" 20 "github.com/koko1123/flow-go-1/engine/protocol" 21 "github.com/koko1123/flow-go-1/model/flow" 22 "github.com/koko1123/flow-go-1/module/metrics" 23 "github.com/koko1123/flow-go-1/utils/grpcutils" 24 ) 25 26 // FlowAccessAPIRouter is a structure that represents the routing proxy algorithm. 27 // It splits requests between a local and a remote API service. 28 type FlowAccessAPIRouter struct { 29 Logger zerolog.Logger 30 Metrics *metrics.ObserverCollector 31 Upstream *FlowAccessAPIForwarder 32 Observer *protocol.Handler 33 } 34 35 func (h *FlowAccessAPIRouter) log(handler, rpc string, err error) { 36 code := status.Code(err) 37 h.Metrics.RecordRPC(handler, rpc, code) 38 39 logger := h.Logger.With(). 40 Str("handler", handler). 41 Str("grpc_method", rpc). 42 Str("grpc_code", code.String()). 43 Logger() 44 45 if err != nil { 46 logger.Error().Err(err).Msg("request failed") 47 return 48 } 49 50 logger.Info().Msg("request succeeded") 51 } 52 53 // reconnectingClient returns an active client, or 54 // creates one, if the last one is not ready anymore. 55 func (h *FlowAccessAPIForwarder) reconnectingClient(i int) error { 56 timeout := h.timeout 57 58 if h.connections[i] == nil || h.connections[i].GetState() != connectivity.Ready { 59 identity := h.ids[i] 60 var connection *grpc.ClientConn 61 var err error 62 if identity.NetworkPubKey == nil { 63 connection, err = grpc.Dial( 64 identity.Address, 65 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), 66 grpc.WithInsecure(), //nolint:staticcheck 67 backend.WithClientUnaryInterceptor(timeout)) 68 if err != nil { 69 return err 70 } 71 } else { 72 tlsConfig, err := grpcutils.DefaultClientTLSConfig(identity.NetworkPubKey) 73 if err != nil { 74 return fmt.Errorf("failed to get default TLS client config using public flow networking key %s %w", identity.NetworkPubKey.String(), err) 75 } 76 77 connection, err = grpc.Dial( 78 identity.Address, 79 grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(grpcutils.DefaultMaxMsgSize)), 80 grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)), 81 backend.WithClientUnaryInterceptor(timeout)) 82 if err != nil { 83 return fmt.Errorf("cannot connect to %s %w", identity.Address, err) 84 } 85 } 86 connection.Connect() 87 time.Sleep(1 * time.Second) 88 state := connection.GetState() 89 if state != connectivity.Ready && state != connectivity.Connecting { 90 return fmt.Errorf("%v", state) 91 } 92 h.connections[i] = connection 93 h.upstream[i] = access.NewAccessAPIClient(connection) 94 } 95 96 return nil 97 } 98 99 // faultTolerantClient implements an upstream connection that reconnects on errors 100 // a reasonable amount of time. 101 func (h *FlowAccessAPIForwarder) faultTolerantClient() (access.AccessAPIClient, error) { 102 if h.upstream == nil || len(h.upstream) == 0 { 103 return nil, status.Errorf(codes.Unimplemented, "method not implemented") 104 } 105 106 // Reasoning: A retry count of three gives an acceptable 5% failure ratio from a 37% failure ratio. 107 // A bigger number is problematic due to the DNS resolve and connection times, 108 // plus the need to log and debug each individual connection failure. 109 // 110 // This reasoning eliminates the need of making this parameter configurable. 111 // The logic works rolling over a single connection as well making clean code. 112 const retryMax = 3 113 114 h.lock.Lock() 115 defer h.lock.Unlock() 116 117 var err error 118 for i := 0; i < retryMax; i++ { 119 h.roundRobin++ 120 h.roundRobin = h.roundRobin % len(h.upstream) 121 err = h.reconnectingClient(h.roundRobin) 122 if err != nil { 123 continue 124 } 125 state := h.connections[h.roundRobin].GetState() 126 if state != connectivity.Ready && state != connectivity.Connecting { 127 continue 128 } 129 return h.upstream[h.roundRobin], nil 130 } 131 132 return nil, status.Errorf(codes.Unavailable, err.Error()) 133 } 134 135 // Ping pings the service. It is special in the sense that it responds successful, 136 // only if all underlying services are ready. 137 func (h *FlowAccessAPIRouter) Ping(context context.Context, req *access.PingRequest) (*access.PingResponse, error) { 138 h.log("observer", "Ping", nil) 139 return &access.PingResponse{}, nil 140 } 141 142 func (h *FlowAccessAPIRouter) GetLatestBlockHeader(context context.Context, req *access.GetLatestBlockHeaderRequest) (*access.BlockHeaderResponse, error) { 143 res, err := h.Observer.GetLatestBlockHeader(context, req) 144 h.log("observer", "GetLatestBlockHeader", err) 145 return res, err 146 } 147 148 func (h *FlowAccessAPIRouter) GetBlockHeaderByID(context context.Context, req *access.GetBlockHeaderByIDRequest) (*access.BlockHeaderResponse, error) { 149 res, err := h.Observer.GetBlockHeaderByID(context, req) 150 h.log("observer", "GetBlockHeaderByID", err) 151 return res, err 152 } 153 154 func (h *FlowAccessAPIRouter) GetBlockHeaderByHeight(context context.Context, req *access.GetBlockHeaderByHeightRequest) (*access.BlockHeaderResponse, error) { 155 res, err := h.Observer.GetBlockHeaderByHeight(context, req) 156 h.log("observer", "GetBlockHeaderByHeight", err) 157 return res, err 158 } 159 160 func (h *FlowAccessAPIRouter) GetLatestBlock(context context.Context, req *access.GetLatestBlockRequest) (*access.BlockResponse, error) { 161 res, err := h.Observer.GetLatestBlock(context, req) 162 h.log("observer", "GetLatestBlock", err) 163 return res, err 164 } 165 166 func (h *FlowAccessAPIRouter) GetBlockByID(context context.Context, req *access.GetBlockByIDRequest) (*access.BlockResponse, error) { 167 res, err := h.Observer.GetBlockByID(context, req) 168 h.log("observer", "GetBlockByID", err) 169 return res, err 170 } 171 172 func (h *FlowAccessAPIRouter) GetBlockByHeight(context context.Context, req *access.GetBlockByHeightRequest) (*access.BlockResponse, error) { 173 res, err := h.Observer.GetBlockByHeight(context, req) 174 h.log("observer", "GetBlockByHeight", err) 175 return res, err 176 } 177 178 func (h *FlowAccessAPIRouter) GetCollectionByID(context context.Context, req *access.GetCollectionByIDRequest) (*access.CollectionResponse, error) { 179 res, err := h.Upstream.GetCollectionByID(context, req) 180 h.log("upstream", "GetCollectionByID", err) 181 return res, err 182 } 183 184 func (h *FlowAccessAPIRouter) SendTransaction(context context.Context, req *access.SendTransactionRequest) (*access.SendTransactionResponse, error) { 185 res, err := h.Upstream.SendTransaction(context, req) 186 h.log("upstream", "SendTransaction", err) 187 return res, err 188 } 189 190 func (h *FlowAccessAPIRouter) GetTransaction(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResponse, error) { 191 res, err := h.Upstream.GetTransaction(context, req) 192 h.log("upstream", "GetTransaction", err) 193 return res, err 194 } 195 196 func (h *FlowAccessAPIRouter) GetTransactionResult(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResultResponse, error) { 197 res, err := h.Upstream.GetTransactionResult(context, req) 198 h.log("upstream", "GetTransactionResult", err) 199 return res, err 200 } 201 202 func (h *FlowAccessAPIRouter) GetTransactionResultsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionResultsResponse, error) { 203 res, err := h.Upstream.GetTransactionResultsByBlockID(context, req) 204 h.log("upstream", "GetTransactionResultsByBlockID", err) 205 return res, err 206 } 207 208 func (h *FlowAccessAPIRouter) GetTransactionsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionsResponse, error) { 209 res, err := h.Upstream.GetTransactionsByBlockID(context, req) 210 h.log("upstream", "GetTransactionsByBlockID", err) 211 return res, err 212 } 213 214 func (h *FlowAccessAPIRouter) GetTransactionResultByIndex(context context.Context, req *access.GetTransactionByIndexRequest) (*access.TransactionResultResponse, error) { 215 res, err := h.Upstream.GetTransactionResultByIndex(context, req) 216 h.log("upstream", "GetTransactionResultByIndex", err) 217 return res, err 218 } 219 220 func (h *FlowAccessAPIRouter) GetAccount(context context.Context, req *access.GetAccountRequest) (*access.GetAccountResponse, error) { 221 res, err := h.Upstream.GetAccount(context, req) 222 h.log("upstream", "GetAccount", err) 223 return res, err 224 } 225 226 func (h *FlowAccessAPIRouter) GetAccountAtLatestBlock(context context.Context, req *access.GetAccountAtLatestBlockRequest) (*access.AccountResponse, error) { 227 res, err := h.Upstream.GetAccountAtLatestBlock(context, req) 228 h.log("upstream", "GetAccountAtLatestBlock", err) 229 return res, err 230 } 231 232 func (h *FlowAccessAPIRouter) GetAccountAtBlockHeight(context context.Context, req *access.GetAccountAtBlockHeightRequest) (*access.AccountResponse, error) { 233 res, err := h.Upstream.GetAccountAtBlockHeight(context, req) 234 h.log("upstream", "GetAccountAtBlockHeight", err) 235 return res, err 236 } 237 238 func (h *FlowAccessAPIRouter) ExecuteScriptAtLatestBlock(context context.Context, req *access.ExecuteScriptAtLatestBlockRequest) (*access.ExecuteScriptResponse, error) { 239 res, err := h.Upstream.ExecuteScriptAtLatestBlock(context, req) 240 h.log("upstream", "ExecuteScriptAtLatestBlock", err) 241 return res, err 242 } 243 244 func (h *FlowAccessAPIRouter) ExecuteScriptAtBlockID(context context.Context, req *access.ExecuteScriptAtBlockIDRequest) (*access.ExecuteScriptResponse, error) { 245 res, err := h.Upstream.ExecuteScriptAtBlockID(context, req) 246 h.log("upstream", "ExecuteScriptAtBlockID", err) 247 return res, err 248 } 249 250 func (h *FlowAccessAPIRouter) ExecuteScriptAtBlockHeight(context context.Context, req *access.ExecuteScriptAtBlockHeightRequest) (*access.ExecuteScriptResponse, error) { 251 res, err := h.Upstream.ExecuteScriptAtBlockHeight(context, req) 252 h.log("upstream", "ExecuteScriptAtBlockHeight", err) 253 return res, err 254 } 255 256 func (h *FlowAccessAPIRouter) GetEventsForHeightRange(context context.Context, req *access.GetEventsForHeightRangeRequest) (*access.EventsResponse, error) { 257 res, err := h.Upstream.GetEventsForHeightRange(context, req) 258 h.log("upstream", "GetEventsForHeightRange", err) 259 return res, err 260 } 261 262 func (h *FlowAccessAPIRouter) GetEventsForBlockIDs(context context.Context, req *access.GetEventsForBlockIDsRequest) (*access.EventsResponse, error) { 263 res, err := h.Upstream.GetEventsForBlockIDs(context, req) 264 h.log("upstream", "GetEventsForBlockIDs", err) 265 return res, err 266 } 267 268 func (h *FlowAccessAPIRouter) GetNetworkParameters(context context.Context, req *access.GetNetworkParametersRequest) (*access.GetNetworkParametersResponse, error) { 269 res, err := h.Observer.GetNetworkParameters(context, req) 270 h.log("observer", "GetNetworkParameters", err) 271 return res, err 272 } 273 274 func (h *FlowAccessAPIRouter) GetLatestProtocolStateSnapshot(context context.Context, req *access.GetLatestProtocolStateSnapshotRequest) (*access.ProtocolStateSnapshotResponse, error) { 275 res, err := h.Observer.GetLatestProtocolStateSnapshot(context, req) 276 h.log("observer", "GetLatestProtocolStateSnapshot", err) 277 return res, err 278 } 279 280 func (h *FlowAccessAPIRouter) GetExecutionResultForBlockID(context context.Context, req *access.GetExecutionResultForBlockIDRequest) (*access.ExecutionResultForBlockIDResponse, error) { 281 res, err := h.Upstream.GetExecutionResultForBlockID(context, req) 282 h.log("upstream", "GetExecutionResultForBlockID", err) 283 return res, err 284 } 285 286 // FlowAccessAPIForwarder forwards all requests to a set of upstream access nodes or observers 287 type FlowAccessAPIForwarder struct { 288 lock sync.Mutex 289 roundRobin int 290 ids flow.IdentityList 291 upstream []access.AccessAPIClient 292 connections []*grpc.ClientConn 293 timeout time.Duration 294 } 295 296 func NewFlowAccessAPIForwarder(identities flow.IdentityList, timeout time.Duration) (*FlowAccessAPIForwarder, error) { 297 forwarder := &FlowAccessAPIForwarder{} 298 err := forwarder.setFlowAccessAPI(identities, timeout) 299 return forwarder, err 300 } 301 302 // setFlowAccessAPI sets a backend access API that forwards some requests to an upstream node. 303 // It is used by Observer services, Blockchain Data Service, etc. 304 // Make sure that this is just for observation and not a staked participant in the flow network. 305 // This means that observers see a copy of the data but there is no interaction to ensure integrity from the root block. 306 func (ret *FlowAccessAPIForwarder) setFlowAccessAPI(accessNodeAddressAndPort flow.IdentityList, timeout time.Duration) error { 307 ret.timeout = timeout 308 ret.ids = accessNodeAddressAndPort 309 ret.upstream = make([]access.AccessAPIClient, accessNodeAddressAndPort.Count()) 310 ret.connections = make([]*grpc.ClientConn, accessNodeAddressAndPort.Count()) 311 for i, identity := range accessNodeAddressAndPort { 312 // Store the faultTolerantClient setup parameters such as address, public, key and timeout, so that 313 // we can refresh the API on connection loss 314 ret.ids[i] = identity 315 316 // We fail on any single error on startup, so that 317 // we identify bootstrapping errors early 318 err := ret.reconnectingClient(i) 319 if err != nil { 320 return err 321 } 322 } 323 324 ret.roundRobin = 0 325 return nil 326 } 327 328 // Ping pings the service. It is special in the sense that it responds successful, 329 // only if all underlying services are ready. 330 func (h *FlowAccessAPIForwarder) Ping(context context.Context, req *access.PingRequest) (*access.PingResponse, error) { 331 // This is a passthrough request 332 upstream, err := h.faultTolerantClient() 333 if err != nil { 334 return nil, err 335 } 336 return upstream.Ping(context, req) 337 } 338 339 func (h *FlowAccessAPIForwarder) GetLatestBlockHeader(context context.Context, req *access.GetLatestBlockHeaderRequest) (*access.BlockHeaderResponse, error) { 340 // This is a passthrough request 341 upstream, err := h.faultTolerantClient() 342 if err != nil { 343 return nil, err 344 } 345 return upstream.GetLatestBlockHeader(context, req) 346 } 347 348 func (h *FlowAccessAPIForwarder) GetBlockHeaderByID(context context.Context, req *access.GetBlockHeaderByIDRequest) (*access.BlockHeaderResponse, error) { 349 // This is a passthrough request 350 upstream, err := h.faultTolerantClient() 351 if err != nil { 352 return nil, err 353 } 354 return upstream.GetBlockHeaderByID(context, req) 355 } 356 357 func (h *FlowAccessAPIForwarder) GetBlockHeaderByHeight(context context.Context, req *access.GetBlockHeaderByHeightRequest) (*access.BlockHeaderResponse, error) { 358 // This is a passthrough request 359 upstream, err := h.faultTolerantClient() 360 if err != nil { 361 return nil, err 362 } 363 return upstream.GetBlockHeaderByHeight(context, req) 364 } 365 366 func (h *FlowAccessAPIForwarder) GetLatestBlock(context context.Context, req *access.GetLatestBlockRequest) (*access.BlockResponse, error) { 367 // This is a passthrough request 368 upstream, err := h.faultTolerantClient() 369 if err != nil { 370 return nil, err 371 } 372 return upstream.GetLatestBlock(context, req) 373 } 374 375 func (h *FlowAccessAPIForwarder) GetBlockByID(context context.Context, req *access.GetBlockByIDRequest) (*access.BlockResponse, error) { 376 // This is a passthrough request 377 upstream, err := h.faultTolerantClient() 378 if err != nil { 379 return nil, err 380 } 381 return upstream.GetBlockByID(context, req) 382 } 383 384 func (h *FlowAccessAPIForwarder) GetBlockByHeight(context context.Context, req *access.GetBlockByHeightRequest) (*access.BlockResponse, error) { 385 // This is a passthrough request 386 upstream, err := h.faultTolerantClient() 387 if err != nil { 388 return nil, err 389 } 390 return upstream.GetBlockByHeight(context, req) 391 } 392 393 func (h *FlowAccessAPIForwarder) GetCollectionByID(context context.Context, req *access.GetCollectionByIDRequest) (*access.CollectionResponse, error) { 394 // This is a passthrough request 395 upstream, err := h.faultTolerantClient() 396 if err != nil { 397 return nil, err 398 } 399 return upstream.GetCollectionByID(context, req) 400 } 401 402 func (h *FlowAccessAPIForwarder) SendTransaction(context context.Context, req *access.SendTransactionRequest) (*access.SendTransactionResponse, error) { 403 // This is a passthrough request 404 upstream, err := h.faultTolerantClient() 405 if err != nil { 406 return nil, err 407 } 408 return upstream.SendTransaction(context, req) 409 } 410 411 func (h *FlowAccessAPIForwarder) GetTransaction(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResponse, error) { 412 // This is a passthrough request 413 upstream, err := h.faultTolerantClient() 414 if err != nil { 415 return nil, err 416 } 417 return upstream.GetTransaction(context, req) 418 } 419 420 func (h *FlowAccessAPIForwarder) GetTransactionResult(context context.Context, req *access.GetTransactionRequest) (*access.TransactionResultResponse, error) { 421 // This is a passthrough request 422 upstream, err := h.faultTolerantClient() 423 if err != nil { 424 return nil, err 425 } 426 return upstream.GetTransactionResult(context, req) 427 } 428 429 func (h *FlowAccessAPIForwarder) GetTransactionResultByIndex(context context.Context, req *access.GetTransactionByIndexRequest) (*access.TransactionResultResponse, error) { 430 // This is a passthrough request 431 upstream, err := h.faultTolerantClient() 432 if err != nil { 433 return nil, err 434 } 435 return upstream.GetTransactionResultByIndex(context, req) 436 } 437 438 func (h *FlowAccessAPIForwarder) GetTransactionResultsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionResultsResponse, error) { 439 // This is a passthrough request 440 upstream, err := h.faultTolerantClient() 441 if err != nil { 442 return nil, err 443 } 444 return upstream.GetTransactionResultsByBlockID(context, req) 445 } 446 447 func (h *FlowAccessAPIForwarder) GetTransactionsByBlockID(context context.Context, req *access.GetTransactionsByBlockIDRequest) (*access.TransactionsResponse, error) { 448 upstream, err := h.faultTolerantClient() 449 if err != nil { 450 return nil, err 451 } 452 return upstream.GetTransactionsByBlockID(context, req) 453 } 454 455 func (h *FlowAccessAPIForwarder) GetAccount(context context.Context, req *access.GetAccountRequest) (*access.GetAccountResponse, error) { 456 // This is a passthrough request 457 upstream, err := h.faultTolerantClient() 458 if err != nil { 459 return nil, err 460 } 461 return upstream.GetAccount(context, req) 462 } 463 464 func (h *FlowAccessAPIForwarder) GetAccountAtLatestBlock(context context.Context, req *access.GetAccountAtLatestBlockRequest) (*access.AccountResponse, error) { 465 // This is a passthrough request 466 upstream, err := h.faultTolerantClient() 467 if err != nil { 468 return nil, err 469 } 470 return upstream.GetAccountAtLatestBlock(context, req) 471 } 472 473 func (h *FlowAccessAPIForwarder) GetAccountAtBlockHeight(context context.Context, req *access.GetAccountAtBlockHeightRequest) (*access.AccountResponse, error) { 474 // This is a passthrough request 475 upstream, err := h.faultTolerantClient() 476 if err != nil { 477 return nil, err 478 } 479 return upstream.GetAccountAtBlockHeight(context, req) 480 } 481 482 func (h *FlowAccessAPIForwarder) ExecuteScriptAtLatestBlock(context context.Context, req *access.ExecuteScriptAtLatestBlockRequest) (*access.ExecuteScriptResponse, error) { 483 // This is a passthrough request 484 upstream, err := h.faultTolerantClient() 485 if err != nil { 486 return nil, err 487 } 488 return upstream.ExecuteScriptAtLatestBlock(context, req) 489 } 490 491 func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockID(context context.Context, req *access.ExecuteScriptAtBlockIDRequest) (*access.ExecuteScriptResponse, error) { 492 // This is a passthrough request 493 upstream, err := h.faultTolerantClient() 494 if err != nil { 495 return nil, err 496 } 497 return upstream.ExecuteScriptAtBlockID(context, req) 498 } 499 500 func (h *FlowAccessAPIForwarder) ExecuteScriptAtBlockHeight(context context.Context, req *access.ExecuteScriptAtBlockHeightRequest) (*access.ExecuteScriptResponse, error) { 501 // This is a passthrough request 502 upstream, err := h.faultTolerantClient() 503 if err != nil { 504 return nil, err 505 } 506 return upstream.ExecuteScriptAtBlockHeight(context, req) 507 } 508 509 func (h *FlowAccessAPIForwarder) GetEventsForHeightRange(context context.Context, req *access.GetEventsForHeightRangeRequest) (*access.EventsResponse, error) { 510 // This is a passthrough request 511 upstream, err := h.faultTolerantClient() 512 if err != nil { 513 return nil, err 514 } 515 return upstream.GetEventsForHeightRange(context, req) 516 } 517 518 func (h *FlowAccessAPIForwarder) GetEventsForBlockIDs(context context.Context, req *access.GetEventsForBlockIDsRequest) (*access.EventsResponse, error) { 519 // This is a passthrough request 520 upstream, err := h.faultTolerantClient() 521 if err != nil { 522 return nil, err 523 } 524 return upstream.GetEventsForBlockIDs(context, req) 525 } 526 527 func (h *FlowAccessAPIForwarder) GetNetworkParameters(context context.Context, req *access.GetNetworkParametersRequest) (*access.GetNetworkParametersResponse, error) { 528 // This is a passthrough request 529 upstream, err := h.faultTolerantClient() 530 if err != nil { 531 return nil, err 532 } 533 return upstream.GetNetworkParameters(context, req) 534 } 535 536 func (h *FlowAccessAPIForwarder) GetLatestProtocolStateSnapshot(context context.Context, req *access.GetLatestProtocolStateSnapshotRequest) (*access.ProtocolStateSnapshotResponse, error) { 537 // This is a passthrough request 538 upstream, err := h.faultTolerantClient() 539 if err != nil { 540 return nil, err 541 } 542 return upstream.GetLatestProtocolStateSnapshot(context, req) 543 } 544 545 func (h *FlowAccessAPIForwarder) GetExecutionResultForBlockID(context context.Context, req *access.GetExecutionResultForBlockIDRequest) (*access.ExecutionResultForBlockIDResponse, error) { 546 // This is a passthrough request 547 upstream, err := h.faultTolerantClient() 548 if err != nil { 549 return nil, err 550 } 551 return upstream.GetExecutionResultForBlockID(context, req) 552 }