Skip to content

[Access] Add endpoints for getting receipts#8480

Merged
peterargue merged 12 commits intomasterfrom
peter/add-execution-receipt-access
Mar 9, 2026
Merged

[Access] Add endpoints for getting receipts#8480
peterargue merged 12 commits intomasterfrom
peter/add-execution-receipt-access

Conversation

@peterargue
Copy link
Contributor

@peterargue peterargue commented Mar 5, 2026

Proto/openapi spec: onflow/flow#1698

This PR adds new methods for getting execution receipts from the Access API.

It adds grpc methods:

  • GetExecutionReceiptsByBlockID
  • GetExecutionReceiptsByResultID

and rest endpoints:

  • /v1/execution_receipts
  • /v1/execution_receipts/results/{id}

This is needed for indexing which execution nodes produced receipts for a given block. It will also make it easier to debug when the network encounters an execution fork.

Summary by CodeRabbit

  • New Features

    • Added execution receipts endpoints (RPC + REST) to fetch receipts by block ID or result ID; responses can inline the execution result or provide a link.
  • Documentation / Models

    • New REST response model for execution receipts with expandable execution_result and base64-encoded binary fields.
  • Tests

    • Added unit, mock, integration, and HTTP route tests covering retrieval, conversion, routing, and end-to-end consistency for execution receipts.

@peterargue peterargue requested a review from a team as a code owner March 5, 2026 01:37
@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
gomod/github.com/onflow/flow/protobuf/go/flow 0.4.20 🟢 5.1
Details
CheckScoreReason
Code-Review⚠️ 2Found 4/14 approved changesets -- score normalized to 2
Security-Policy🟢 10security policy file detected
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 9binaries present in source code
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Packaging⚠️ -1packaging workflow not detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
SAST🟢 7SAST tool detected but not run on all commits
gomod/github.com/onflow/flow/protobuf/go/flow 0.4.20 🟢 5.1
Details
CheckScoreReason
Code-Review⚠️ 2Found 4/14 approved changesets -- score normalized to 2
Security-Policy🟢 10security policy file detected
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 9binaries present in source code
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Packaging⚠️ -1packaging workflow not detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
SAST🟢 7SAST tool detected but not run on all commits
gomod/github.com/onflow/flow/protobuf/go/flow 0.4.20 🟢 5.1
Details
CheckScoreReason
Code-Review⚠️ 2Found 4/14 approved changesets -- score normalized to 2
Security-Policy🟢 10security policy file detected
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 9binaries present in source code
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Packaging⚠️ -1packaging workflow not detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
Signed-Releases⚠️ -1no releases found
SAST🟢 7SAST tool detected but not run on all commits

Scanned Files

  • go.mod
  • insecure/go.mod
  • integration/go.mod

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a ReceiptsAPI and end-to-end support for querying execution receipts by block ID and by result ID across API surfaces, mocks, RPC/REST handlers, backend storage wiring, conversion utilities, REST models/routes, and tests.

Changes

Cohort / File(s) Summary
API surface & unimplemented
access/api.go, access/unimplemented.go
Add ReceiptsAPI interface with GetExecutionReceiptsByBlockID and GetExecutionReceiptsByResultID; embed in API and add unimplemented stubs.
Access mocks
access/mock/api.go, access/mock/receipts_api.go
Add Testify-style mocks, Expecter, Run/Return/RunAndReturn helpers and constructor for ReceiptsAPI methods.
Engine apiproxy & forwarder
engine/access/apiproxy/access_api_proxy.go
Add router RPC handlers and forwarder passthroughs for receipts RPCs (local index path + upstream forwarding).
Engine access client/server mocks
engine/access/mock/access_api_client.go, engine/access/mock/access_api_server.go
Generated mock client/server methods, Call types, and Expecter/Run/Return helpers for new RPCs.
Backend wiring & logic
engine/access/rpc/backend/backend.go, engine/access/rpc/backend/backend_execution_results.go
Wire storage.ExecutionReceipts into backendExecutionResults; implement GetExecutionReceiptsByBlockID and GetExecutionReceiptsByResultID (resolve result→block then filter).
Backend tests
engine/access/rpc/backend/backend_execution_results_test.go, engine/access/rpc/backend/backend_test.go
Unit tests covering receipts retrieval: success, not-found, storage errors and irrecoverable paths.
RPC handler & conversion
engine/access/rpc/handler.go, engine/common/rpc/convert/execution_results.go, engine/common/rpc/convert/execution_results_test.go
Add Handler RPC methods returning ExecutionReceiptsResponse; add conversions ExecutionReceipt(s)→protobuf with optional embedded ExecutionResult and tests.
REST models & builders
engine/access/rest/common/models/model_execution_receipt.go, ...__expandable.go, engine/access/rest/common/models/execution_receipt.go, engine/access/rest/http/request/get_execution_receipt.go
Add REST ExecutionReceipt model and expandable type; Build method (base64 fields, inline or link ExecutionResult); request builders parse/validate block_id and result_id.
REST routes & tests
engine/access/rest/http/routes/execution_receipts.go, .../execution_receipts_test.go, engine/access/rest/router/routes_main.go, engine/access/rest/router/metrics_test.go
Add HTTP handlers and routes (/execution_receipts, /execution_receipts/results/{id}); wire into router and add route/unit tests for happy and error cases.
Engine RPC mocks & proxy tests
engine/access/mock/*
Expand engine/access mock files to support new RPCs (generated additions across mock files).
Integration tests & suites
integration/tests/access/cohort1/..., integration/tests/access/cohort2/...
Add integration tests validating receipts↔results consistency and exercise RPC/REST endpoints in observer/indexer suites.
Module bumps
go.mod, insecure/go.mod, integration/go.mod
Bump protobuf flow dependency from v0.4.19 → v0.4.20-0.20260304213159-636750371b10 across modules.
Storage docs
storage/receipts.go
Minor comment clarification for ByBlockID behavior (no functional change).

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (REST/gRPC)
  participant Router as API Router / Handler
  participant Backend as backendExecutionResults
  participant Storage as ExecutionReceipts Storage
  participant Converter as convert.ExecutionReceiptsToMessages

  rect rgba(200,230,255,0.5)
  Client->>Router: GetExecutionReceiptsByBlockID(blockID) or GetExecutionReceiptsByResultID(resultID)
  end

  rect rgba(180,255,200,0.5)
  Router->>Backend: GetExecutionReceiptsByBlockID(blockID) / resolve resultID -> GetExecutionReceiptsByBlockID(blockID)
  Backend->>Storage: Query receipts ByBlockID(blockID)
  Storage-->>Backend: []*flow.ExecutionReceipt
  Backend-->>Router: []*flow.ExecutionReceipt
  end

  rect rgba(255,240,200,0.5)
  Router->>Converter: convert.ExecutionReceiptsToMessages(receipts, includeResult?)
  Converter-->>Router: protobuf messages
  Router-->>Client: ExecutionReceiptsResponse / JSON array
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • zhangchiqing
  • fxamacker

Poem

🐰 I hopped through code to fetch each receipt,
Block and result matched in a tiny beat,
Mocks and routes, converters in a row,
Tests sang "green" and links did glow,
Hooray — receipts snug, compiled and neat!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.87% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding new endpoints for retrieving execution receipts as part of the Access API.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch peter/add-execution-receipt-access

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
engine/access/rpc/backend/backend.go (1)

315-315: Fail fast if ExecutionReceipts is missing in New.

Since the new receipt APIs dereference this dependency, adding constructor validation avoids nil-deref failures at runtime.

Suggested patch
 func New(params Params) (*Backend, error) {
+    if params.ExecutionReceipts == nil {
+        return nil, fmt.Errorf("missing required dependency: ExecutionReceipts")
+    }
+
     loggedScripts, err := lru.New[[md5.Size]byte, time.Time](common.DefaultLoggedScriptsCacheSize)

As per coding guidelines Use dependency injection patterns for component composition and implement proper interfaces before concrete types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rpc/backend/backend.go` at line 315, The constructor New in
backend.go should validate that params.ExecutionReceipts is non-nil and return
an error immediately if missing to avoid nil-dereference later; update the New
function (the code that sets receipts: params.ExecutionReceipts) to check
params.ExecutionReceipts at the start, return a descriptive error (e.g.,
"missing ExecutionReceipts") instead of constructing the backend, and keep the
rest of the initialization unchanged so callers must inject a valid
ExecutionReceipts implementation.
integration/tests/access/cohort1/access_api_test.go (1)

1472-1480: Make the receipt set key explicit to avoid ambiguous concatenation.

Using direct string concatenation for composite identity is brittle; add a delimiter (or use a small struct key) for clearer intent and safer matching.

♻️ Small clarity refactor
-		key := convert.MessageToIdentifier(receipt.Meta.ExecutorId).String() +
-			convert.MessageToIdentifier(receipt.Meta.ResultId).String()
+		key := convert.MessageToIdentifier(receipt.Meta.ExecutorId).String() + ":" +
+			convert.MessageToIdentifier(receipt.Meta.ResultId).String()
-		key := convert.MessageToIdentifier(receipt.Meta.ExecutorId).String() +
-			convert.MessageToIdentifier(receipt.Meta.ResultId).String()
+		key := convert.MessageToIdentifier(receipt.Meta.ExecutorId).String() + ":" +
+			convert.MessageToIdentifier(receipt.Meta.ResultId).String()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integration/tests/access/cohort1/access_api_test.go` around lines 1472 -
1480, The composite key built in the loops (variable key in the receipts
iteration that uses convert.MessageToIdentifier(...) for receipt.Meta.ExecutorId
and receipt.Meta.ResultId) is formed by naive string concatenation which can be
ambiguous; change the key to include an explicit delimiter (e.g. executorId +
"|" + resultId) or replace the string key with a small struct type (e.g. type
receiptKey struct { Executor, Result string } used in byBlockSet map) so
byBlockSet, receiptsByBlock and receiptsByResult comparisons are unambiguous.
engine/access/rest/http/routes/execution_receipts_test.go (1)

57-125: Add malformed-ID negative tests for both receipt endpoints.

Coverage is good for success and NotFound, but request parsing for malformed block_id and malformed {resultID} is not asserted. Adding those cases will protect the validation paths from regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rest/http/routes/execution_receipts_test.go` around lines 57 -
125, Add negative subtests to TestGetExecutionReceiptsByBlockID and
TestGetExecutionReceiptsByResultID that exercise malformed IDs: call
getReceiptsByBlockIDReq with an invalid block_id string (e.g. "not-a-valid-id")
and call getReceiptsByResultIDReq with an invalid result id path segment, then
assert the router returns HTTP 400 and the appropriate JSON error body for
invalid parameters. Place these new subtests alongside the existing cases in
TestGetExecutionReceiptsByBlockID and TestGetExecutionReceiptsByResultID; they
do not need backend mocks (do not set expectations on mock.API) and should
validate request parsing/validation for getReceiptsByBlockIDReq and
getReceiptsByResultIDReq.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/access/apiproxy/access_api_proxy.go`:
- Around line 441-451: Both GetExecutionReceiptsByBlockID and
GetExecutionReceiptsByResultID always call the upstream, bypassing the local
index; update them to check the router's index flag and use the local index
handler when enabled. In each function (GetExecutionReceiptsByBlockID and
GetExecutionReceiptsByResultID) branch on h.useIndex (or the existing
local-index boolean/field) and call the corresponding index method (e.g.,
h.index.GetExecutionReceiptsByBlockID / h.index.GetExecutionReceiptsByResultID)
when true, otherwise fall back to h.upstream. Ensure you keep the same logging
pattern (h.log with UpstreamApiService or an IndexApiService tag) and return the
response and error as before.

In `@engine/access/rest/common/models/execution_receipt.go`:
- Around line 17-23: The Build method on ExecutionReceipt dereferences receipt
and link immediately which will panic on nil inputs; add defensive checks at the
start of ExecutionReceipt.Build to validate receipt and link (and any required
nested fields like receipt.ExecutionResult or receipt.ExecutorID) and return a
descriptive error (e.g., wrapped or typed) when they are nil or invalid instead
of proceeding; locate the Build function and insert nil checks for the receipt
parameter and the link parameter and validate
receipt.ExecutionResult/receipt.ExecutorID before using them, returning a clear,
classified error to the caller.

In `@engine/access/rest/http/request/get_execution_receipt.go`:
- Line 20: The comment saying "No errors are expected during normal operation."
is inaccurate because the request builders validate inputs and explicitly return
errors for missing or malformed execution IDs; update the comment(s) in
get_execution_receipt.go to state that the builders (e.g.,
GetExecutionReceiptRequestBuilder and its Build/Validate methods) will return
errors if required IDs are missing or malformed so the documented error contract
matches the actual behavior (also update the duplicate comment near the second
builder mentioned).

In `@engine/access/rpc/backend/backend_execution_results.go`:
- Around line 56-58: Replace fmt.Errorf(...) usage with
irrecoverable.NewExceptionf(...) when constructing exceptions before calling
irrecoverable.Throw. Specifically, in the function that calls
irrecoverable.Throw(ctx, err) (the blocks currently using fmt.Errorf("failed to
get execution receipts by block ID: %w", err) and the two other similar blocks
at the later commented ranges), change those fmt.Errorf calls to err =
irrecoverable.NewExceptionf("failed to get execution receipts by block ID: %v",
err) (or the matching message used in the other two locations) and leave the
subsequent irrecoverable.Throw(ctx, err) call unchanged; use
irrecoverable.NewExceptionf and irrecoverable.Throw together everywhere instead
of fmt.Errorf.
- Around line 94-96: Guard against nil receipt entries before dereferencing by
checking receipt and its ExecutionResult before calling
receipt.ExecutionResult.ID(): inside the loop over allReceipts add a defensive
if receipt == nil || receipt.ExecutionResult == nil { continue } so you skip nil
entries and avoid panics when comparing to resultID; update any related logic
that assumes non-nil receipts accordingly (look for the loop using allReceipts
and the comparison receipt.ExecutionResult.ID() == resultID in
backend_execution_results.go).

In `@engine/access/rpc/handler.go`:
- Line 1120: Validate incoming request ID payloads before calling
convert.MessageToIdentifier: check req.GetBlockId() (and the other ID used at
the second call) for nil/empty and run explicit validation/parse logic (e.g.,
base64/UUID/expected format decode) and return a context-appropriate error if
validation fails instead of passing malformed data into
convert.MessageToIdentifier; update the handler function around the uses of
convert.MessageToIdentifier to perform these checks and short-circuit with a
clear client-facing error (including which field failed) so invalid IDs are
rejected upstream rather than silently coerced.

In `@engine/common/rpc/convert/execution_results.go`:
- Around line 151-170: ExecutionReceiptToMessage currently dereferences receipt
and its nested fields (e.g., receipt.ExecutorID, receipt.ExecutionResult,
receipt.Spocks, receipt.ExecutorSignature) and can panic on nil inputs; add
defensive nil guards at the start of ExecutionReceiptToMessage to return a clear
error if receipt is nil and also verify receipt.ExecutionResult is non-nil
before calling ExecutionResultToMessage when includeResult is true (return a
descriptive error like "nil execution result in receipt"); ensure any sub-field
dereferences (ExecutorID, Spocks, ExecutorSignature) are safe or documented as
required so the function fails closed with explicit errors rather than
panicking.

In `@integration/tests/access/cohort2/observer_indexer_enabled_test.go`:
- Around line 685-696: Update the SetupTest in observer_indexer_enabled_test.go
to classify the new receipt endpoints the same way as the base observer tests:
add "getExecutionReceiptsByBlockID" and "getExecutionReceiptsByResultID" to the
localRest map used in SetupTest so they are treated as locally handled REST
endpoints (match how observer_test.go does for
getExecutionReceiptsByBlockID/getExecutionReceiptsByResultID); also inspect the
localRpc map in SetupTest and add or verify the receipt RPC entries there if RPC
handling differs from the base suite so localRpc and localRest classifications
remain consistent with getRPCs/getRestEndpoints and the other observer tests.

In `@integration/tests/access/cohort2/observer_test.go`:
- Around line 73-81: The localRpc map is missing the RPC method names for
execution receipts, causing inconsistent handling vs localRest; update the
localRpc initialization (the map named localRpc) to include
"GetExecutionReceiptsByBlockID" and "GetExecutionReceiptsByResultID" (exact
strings) so RPC calls are treated as locally handled by the observer like the
REST endpoints (which contain
getExecutionReceiptsByBlockID/getExecutionReceiptsByResultID); ensure you add
those two keys to the same map literal where other RPC methods are listed.

---

Nitpick comments:
In `@engine/access/rest/http/routes/execution_receipts_test.go`:
- Around line 57-125: Add negative subtests to TestGetExecutionReceiptsByBlockID
and TestGetExecutionReceiptsByResultID that exercise malformed IDs: call
getReceiptsByBlockIDReq with an invalid block_id string (e.g. "not-a-valid-id")
and call getReceiptsByResultIDReq with an invalid result id path segment, then
assert the router returns HTTP 400 and the appropriate JSON error body for
invalid parameters. Place these new subtests alongside the existing cases in
TestGetExecutionReceiptsByBlockID and TestGetExecutionReceiptsByResultID; they
do not need backend mocks (do not set expectations on mock.API) and should
validate request parsing/validation for getReceiptsByBlockIDReq and
getReceiptsByResultIDReq.

In `@engine/access/rpc/backend/backend.go`:
- Line 315: The constructor New in backend.go should validate that
params.ExecutionReceipts is non-nil and return an error immediately if missing
to avoid nil-dereference later; update the New function (the code that sets
receipts: params.ExecutionReceipts) to check params.ExecutionReceipts at the
start, return a descriptive error (e.g., "missing ExecutionReceipts") instead of
constructing the backend, and keep the rest of the initialization unchanged so
callers must inject a valid ExecutionReceipts implementation.

In `@integration/tests/access/cohort1/access_api_test.go`:
- Around line 1472-1480: The composite key built in the loops (variable key in
the receipts iteration that uses convert.MessageToIdentifier(...) for
receipt.Meta.ExecutorId and receipt.Meta.ResultId) is formed by naive string
concatenation which can be ambiguous; change the key to include an explicit
delimiter (e.g. executorId + "|" + resultId) or replace the string key with a
small struct type (e.g. type receiptKey struct { Executor, Result string } used
in byBlockSet map) so byBlockSet, receiptsByBlock and receiptsByResult
comparisons are unambiguous.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9bacf739-74f9-4d83-8b76-ccff09cf5608

📥 Commits

Reviewing files that changed from the base of the PR and between b55ff64 and 5ee6dee.

⛔ Files ignored due to path filters (3)
  • go.sum is excluded by !**/*.sum
  • insecure/go.sum is excluded by !**/*.sum
  • integration/go.sum is excluded by !**/*.sum
📒 Files selected for processing (28)
  • access/api.go
  • access/mock/api.go
  • access/mock/receipts_api.go
  • access/unimplemented.go
  • engine/access/apiproxy/access_api_proxy.go
  • engine/access/mock/access_api_client.go
  • engine/access/mock/access_api_server.go
  • engine/access/rest/common/models/execution_receipt.go
  • engine/access/rest/common/models/model_execution_receipt.go
  • engine/access/rest/common/models/model_execution_receipt__expandable.go
  • engine/access/rest/http/request/get_execution_receipt.go
  • engine/access/rest/http/routes/execution_receipts.go
  • engine/access/rest/http/routes/execution_receipts_test.go
  • engine/access/rest/router/metrics_test.go
  • engine/access/rest/router/routes_main.go
  • engine/access/rpc/backend/backend.go
  • engine/access/rpc/backend/backend_execution_results.go
  • engine/access/rpc/backend/backend_execution_results_test.go
  • engine/access/rpc/backend/backend_test.go
  • engine/access/rpc/handler.go
  • engine/common/rpc/convert/execution_results.go
  • engine/common/rpc/convert/execution_results_test.go
  • go.mod
  • insecure/go.mod
  • integration/go.mod
  • integration/tests/access/cohort1/access_api_test.go
  • integration/tests/access/cohort2/observer_indexer_enabled_test.go
  • integration/tests/access/cohort2/observer_test.go

Comment on lines +441 to +451
func (h *FlowAccessAPIRouter) GetExecutionReceiptsByBlockID(context context.Context, req *access.GetExecutionReceiptsByBlockIDRequest) (*access.ExecutionReceiptsResponse, error) {
res, err := h.upstream.GetExecutionReceiptsByBlockID(context, req)
h.log(UpstreamApiService, "GetExecutionReceiptsByBlockID", err)
return res, err
}

func (h *FlowAccessAPIRouter) GetExecutionReceiptsByResultID(context context.Context, req *access.GetExecutionReceiptsByResultIDRequest) (*access.ExecutionReceiptsResponse, error) {
res, err := h.upstream.GetExecutionReceiptsByResultID(context, req)
h.log(UpstreamApiService, "GetExecutionReceiptsByResultID", err)
return res, err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

useIndex is bypassed for new receipts reads.

On Line 441 and Line 447, both methods always route upstream. This makes useIndex ineffective for receipts and can add unnecessary upstream dependency/latency when local indexing is enabled.

🔧 Suggested routing parity fix
 func (h *FlowAccessAPIRouter) GetExecutionReceiptsByBlockID(context context.Context, req *access.GetExecutionReceiptsByBlockIDRequest) (*access.ExecutionReceiptsResponse, error) {
+	if h.useIndex {
+		res, err := h.local.GetExecutionReceiptsByBlockID(context, req)
+		h.log(LocalApiService, "GetExecutionReceiptsByBlockID", err)
+		return res, err
+	}
+
 	res, err := h.upstream.GetExecutionReceiptsByBlockID(context, req)
 	h.log(UpstreamApiService, "GetExecutionReceiptsByBlockID", err)
 	return res, err
 }
 
 func (h *FlowAccessAPIRouter) GetExecutionReceiptsByResultID(context context.Context, req *access.GetExecutionReceiptsByResultIDRequest) (*access.ExecutionReceiptsResponse, error) {
+	if h.useIndex {
+		res, err := h.local.GetExecutionReceiptsByResultID(context, req)
+		h.log(LocalApiService, "GetExecutionReceiptsByResultID", err)
+		return res, err
+	}
+
 	res, err := h.upstream.GetExecutionReceiptsByResultID(context, req)
 	h.log(UpstreamApiService, "GetExecutionReceiptsByResultID", err)
 	return res, err
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/apiproxy/access_api_proxy.go` around lines 441 - 451, Both
GetExecutionReceiptsByBlockID and GetExecutionReceiptsByResultID always call the
upstream, bypassing the local index; update them to check the router's index
flag and use the local index handler when enabled. In each function
(GetExecutionReceiptsByBlockID and GetExecutionReceiptsByResultID) branch on
h.useIndex (or the existing local-index boolean/field) and call the
corresponding index method (e.g., h.index.GetExecutionReceiptsByBlockID /
h.index.GetExecutionReceiptsByResultID) when true, otherwise fall back to
h.upstream. Ensure you keep the same logging pattern (h.log with
UpstreamApiService or an IndexApiService tag) and return the response and error
as before.

Comment on lines +17 to +23
func (e *ExecutionReceipt) Build(
receipt *flow.ExecutionReceipt,
link LinkGenerator,
expand map[string]bool,
) error {
e.ExecutorId = receipt.ExecutorID.String()
e.ResultId = receipt.ExecutionResult.ID().String()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard Build inputs to prevent panic paths.

Build dereferences receipt and link immediately; nil inputs will panic instead of returning a classified error.

🛡️ Proposed hardening
 import (
+	"errors"
 	"fmt"
 
 	"github.com/onflow/flow-go/engine/access/rest/util"
 	"github.com/onflow/flow-go/model/flow"
 )
@@
 func (e *ExecutionReceipt) Build(
 	receipt *flow.ExecutionReceipt,
 	link LinkGenerator,
 	expand map[string]bool,
 ) error {
+	if receipt == nil {
+		return errors.New("execution receipt is nil")
+	}
+	if link == nil {
+		return errors.New("link generator is nil")
+	}
+
 	e.ExecutorId = receipt.ExecutorID.String()
 	e.ResultId = receipt.ExecutionResult.ID().String()
As per coding guidelines "Treat all inputs as potentially byzantine and classify errors in a context-dependent manner; no code path is safe unless explicitly proven and documented".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rest/common/models/execution_receipt.go` around lines 17 - 23,
The Build method on ExecutionReceipt dereferences receipt and link immediately
which will panic on nil inputs; add defensive checks at the start of
ExecutionReceipt.Build to validate receipt and link (and any required nested
fields like receipt.ExecutionResult or receipt.ExecutorID) and return a
descriptive error (e.g., wrapped or typed) when they are nil or invalid instead
of proceeding; locate the Build function and insert nil checks for the receipt
parameter and the link parameter and validate
receipt.ExecutionResult/receipt.ExecutorID before using them, returning a clear,
classified error to the caller.

// GetExecutionReceiptsByBlockIDRequest extracts necessary variables from the provided request,
// builds a GetExecutionReceiptsByBlockID instance, and validates it.
//
// No errors are expected during normal operation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Commented error contract does not match implementation.

Line 20 and Line 49 say “No errors are expected during normal operation,” but these builders explicitly error on missing/malformed IDs. Please align the comments with the real behavior.

Also applies to: 49-49

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rest/http/request/get_execution_receipt.go` at line 20, The
comment saying "No errors are expected during normal operation." is inaccurate
because the request builders validate inputs and explicitly return errors for
missing or malformed execution IDs; update the comment(s) in
get_execution_receipt.go to state that the builders (e.g.,
GetExecutionReceiptRequestBuilder and its Build/Validate methods) will return
errors if required IDs are missing or malformed so the documented error contract
matches the actual behavior (also update the duplicate comment near the second
builder mentioned).

Comment on lines +56 to +58
err = fmt.Errorf("failed to get execution receipts by block ID: %w", err)
irrecoverable.Throw(ctx, err)
return nil, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd /tmp/codebase && wc -l engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 121


🏁 Script executed:

cd /tmp/codebase && cat -n engine/access/rpc/backend/backend_execution_results.go | sed -n '50,100p'

Repository: onflow/flow-go

Length of output: 121


🏁 Script executed:

cd /tmp/codebase && rg -n 'irrecoverable\.' engine/access/rpc/backend/backend_execution_results.go | head -20

Repository: onflow/flow-go

Length of output: 121


🏁 Script executed:

cd /tmp/codebase && rg -n 'NewExceptionf' --type go | head -10

Repository: onflow/flow-go

Length of output: 121


🏁 Script executed:

wc -l engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 116


🏁 Script executed:

cat -n engine/access/rpc/backend/backend_execution_results.go | sed -n '50,100p'

Repository: onflow/flow-go

Length of output: 2607


🏁 Script executed:

rg -n 'irrecoverable\.' engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 165


🏁 Script executed:

rg -n 'NewExceptionf' --type go | head -20

Repository: onflow/flow-go

Length of output: 2634


🏁 Script executed:

fd -t f 'irrecoverable' | grep -E '\.go$' | head -5

Repository: onflow/flow-go

Length of output: 227


🏁 Script executed:

rg -n 'func NewExceptionf' --type go

Repository: onflow/flow-go

Length of output: 146


🏁 Script executed:

cat -n module/irrecoverable/irrecoverable.go | head -50

Repository: onflow/flow-go

Length of output: 1775


🏁 Script executed:

rg -B2 -A2 'irrecoverable\.Throw.*err' engine/ --type go | head -40

Repository: onflow/flow-go

Length of output: 3238


Use irrecoverable.NewExceptionf() instead of fmt.Errorf() for exception construction.

Storage failures that are not ErrNotFound are treated as irrecoverable, but the current code wraps them with fmt.Errorf() before passing to irrecoverable.Throw(). Per the project guidelines, use irrecoverable.NewExceptionf() to construct the exception directly.

Suggested patch
-           err = fmt.Errorf("failed to get execution receipts by block ID: %w", err)
-           irrecoverable.Throw(ctx, err)
-           return nil, err
+           exception := irrecoverable.NewExceptionf("failed to get execution receipts by block ID: %w", err)
+           irrecoverable.Throw(ctx, exception)
+           return nil, exception
-           err = fmt.Errorf("failed to get execution result: %w", err)
-           irrecoverable.Throw(ctx, err)
-           return nil, err
+           exception := irrecoverable.NewExceptionf("failed to get execution result: %w", err)
+           irrecoverable.Throw(ctx, exception)
+           return nil, exception
-           err = fmt.Errorf("failed to get execution receipts by result ID: %w", err)
-           irrecoverable.Throw(ctx, err)
-           return nil, err
+           exception := irrecoverable.NewExceptionf("failed to get execution receipts by result ID: %w", err)
+           irrecoverable.Throw(ctx, exception)
+           return nil, exception

Also applies to: lines 75-77, 86-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rpc/backend/backend_execution_results.go` around lines 56 - 58,
Replace fmt.Errorf(...) usage with irrecoverable.NewExceptionf(...) when
constructing exceptions before calling irrecoverable.Throw. Specifically, in the
function that calls irrecoverable.Throw(ctx, err) (the blocks currently using
fmt.Errorf("failed to get execution receipts by block ID: %w", err) and the two
other similar blocks at the later commented ranges), change those fmt.Errorf
calls to err = irrecoverable.NewExceptionf("failed to get execution receipts by
block ID: %v", err) (or the matching message used in the other two locations)
and leave the subsequent irrecoverable.Throw(ctx, err) call unchanged; use
irrecoverable.NewExceptionf and irrecoverable.Throw together everywhere instead
of fmt.Errorf.

Comment on lines +94 to +96
for _, receipt := range allReceipts {
if receipt.ExecutionResult.ID() == resultID {
receipts = append(receipts, receipt)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, examine the full context of the function containing lines 94-96
wc -l engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Get context around the flagged lines
sed -n '80,110p' engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 727


🏁 Script executed:

#!/bin/bash
# Identify the function name and full signature
sed -n '1,100p' engine/access/rpc/backend/backend_execution_results.go | grep -B20 'for _, receipt := range allReceipts' | head -40

Repository: onflow/flow-go

Length of output: 859


🏁 Script executed:

#!/bin/bash
# Find the receipts field definition and its type
rg -n 'receipts\s+' engine/access/rpc/backend/backend_execution_results.go | head -5

Repository: onflow/flow-go

Length of output: 462


🏁 Script executed:

#!/bin/bash
# Search for the type of b.receipts and the ByBlockID method
fd -e go backend.go | head -5

Repository: onflow/flow-go

Length of output: 278


🏁 Script executed:

#!/bin/bash
# Look at the backend structure definition
rg -n 'type.*Backend\s*struct' engine/access/rpc/backend/ -A30

Repository: onflow/flow-go

Length of output: 2171


🏁 Script executed:

#!/bin/bash
# Search for ByBlockID method definition
rg -n 'func.*ByBlockID' engine/access/rpc/backend/ -A5

Repository: onflow/flow-go

Length of output: 41036


🏁 Script executed:

#!/bin/bash
# Find storage.ExecutionReceipts interface definition
fd -e go storage | head -20

Repository: onflow/flow-go

Length of output: 986


🏁 Script executed:

#!/bin/bash
# Search for ExecutionReceipts interface in storage
rg -n 'type ExecutionReceipts' --type go

Repository: onflow/flow-go

Length of output: 1082


🏁 Script executed:

#!/bin/bash
# Search for ByBlockID method signature and documentation in storage
rg -n 'ByBlockID.*ExecutionReceipt' --type go -A3 -B1

Repository: onflow/flow-go

Length of output: 27304


🏁 Script executed:

#!/bin/bash
# Find flow.ExecutionReceiptList type definition
rg -n 'type ExecutionReceiptList' --type go

Repository: onflow/flow-go

Length of output: 139


🏁 Script executed:

#!/bin/bash
# Get full implementation of byBlockID method
sed -n '110,150p' storage/store/receipts.go

Repository: onflow/flow-go

Length of output: 346


🏁 Script executed:

#!/antml:bash
# Look for the complete byBlockID implementation in receipts.go
cat -n storage/store/receipts.go | sed -n '100,200p'

Repository: onflow/flow-go

Length of output: 833


🏁 Script executed:

#!/bin/bash
# Search for the actual byBlockID implementation (lowercase)
rg -n 'func.*byBlockID' storage/store/receipts.go -A20

Repository: onflow/flow-go

Length of output: 881


🏁 Script executed:

#!/bin/bash
# Also check the ExecutionReceipt type definition to understand dereferences
rg -n 'type ExecutionReceipt struct' model/flow/execution_receipt.go -A10

Repository: onflow/flow-go

Length of output: 507


🏁 Script executed:

#!/bin/bash
# Check if there are any nil guards in the filtered loop in tests
rg -n 'for.*receipt.*range.*allReceipts' --type go -A5

Repository: onflow/flow-go

Length of output: 561


🏁 Script executed:

#!/bin/bash
# Search for similar patterns where code iterates over slices and dereferences without nil guards
rg -n 'for.*range.*\[\]\*.*{' --type go -A3 | grep -A3 'ExecutionReceipt\|receipt\.' | head -40

Repository: onflow/flow-go

Length of output: 163


🏁 Script executed:

#!/bin/bash
# Check if there are explicit nil checks for receipt entries elsewhere in the codebase
rg -n 'if.*receipt.*==.*nil' --type go | head -20

Repository: onflow/flow-go

Length of output: 152


🏁 Script executed:

#!/bin/bash
# Verify the full function context including error handling to understand the expected invariant
sed -n '70,100p' engine/access/rpc/backend/backend_execution_results.go

Repository: onflow/flow-go

Length of output: 1281


🏁 Script executed:

#!/bin/bash
# Check the storage.ExecutionReceipts interface definition for documentation
cat -n storage/receipts.go | sed -n '20,35p'

Repository: onflow/flow-go

Length of output: 824


🏁 Script executed:

#!/bin/bash
# Check if there are any other methods that iterate over ExecutionReceiptList with nil guards
rg -n 'ExecutionReceiptList' --type go -B2 -A8 | grep -A8 'for.*range'

Repository: onflow/flow-go

Length of output: 8514


🏁 Script executed:

#!/bin/bash
# Examine the ByID implementation to confirm whether it can return nil pointer in normal operation
sed -n '50,80p' storage/store/receipts.go

Repository: onflow/flow-go

Length of output: 946


Guard against nil receipt entries before dereferencing.

At line 95, receipt.ExecutionResult.ID() could panic if any entry in allReceipts is nil. While the current byBlockID implementation handles errors properly and prevents nil entries in normal operation, add a defensive guard aligned with coding guidelines for treating all inputs as potentially unsafe.

Suggested patch
  for _, receipt := range allReceipts {
+     if receipt == nil {
+         exception := irrecoverable.NewExceptionf("nil execution receipt while filtering by result ID: %x", resultID)
+         irrecoverable.Throw(ctx, exception)
+         return nil, exception
+     }
      if receipt.ExecutionResult.ID() == resultID {
          receipts = append(receipts, receipt)
      }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rpc/backend/backend_execution_results.go` around lines 94 - 96,
Guard against nil receipt entries before dereferencing by checking receipt and
its ExecutionResult before calling receipt.ExecutionResult.ID(): inside the loop
over allReceipts add a defensive if receipt == nil || receipt.ExecutionResult ==
nil { continue } so you skip nil entries and avoid panics when comparing to
resultID; update any related logic that assumes non-nil receipts accordingly
(look for the loop using allReceipts and the comparison
receipt.ExecutionResult.ID() == resultID in backend_execution_results.go).

return nil, err
}

blockID := convert.MessageToIdentifier(req.GetBlockId())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate request IDs before converting to internal identifiers.

Line 1120 and Line 1149 use convert.MessageToIdentifier(...), which does not reject malformed ID payloads. This can silently coerce invalid client input and misclassify request errors downstream.

🔧 Suggested fix
 func (h *Handler) GetExecutionReceiptsByBlockID(ctx context.Context, req *accessproto.GetExecutionReceiptsByBlockIDRequest) (*accessproto.ExecutionReceiptsResponse, error) {
 	metadata, err := h.buildMetadataResponse()
 	if err != nil {
 		return nil, err
 	}
 
-	blockID := convert.MessageToIdentifier(req.GetBlockId())
+	blockID, err := convert.BlockID(req.GetBlockId())
+	if err != nil {
+		return nil, status.Errorf(codes.InvalidArgument, "invalid block id: %v", err)
+	}
 
 	receipts, err := h.api.GetExecutionReceiptsByBlockID(ctx, blockID)
 	if err != nil {
 		return nil, err
 	}
@@
 func (h *Handler) GetExecutionReceiptsByResultID(ctx context.Context, req *accessproto.GetExecutionReceiptsByResultIDRequest) (*accessproto.ExecutionReceiptsResponse, error) {
 	metadata, err := h.buildMetadataResponse()
 	if err != nil {
 		return nil, err
 	}
 
-	resultID := convert.MessageToIdentifier(req.GetResultId())
+	resultID, err := convert.BlockID(req.GetResultId()) // or a dedicated validating identifier parser
+	if err != nil {
+		return nil, status.Errorf(codes.InvalidArgument, "invalid result id: %v", err)
+	}
 
 	receipts, err := h.api.GetExecutionReceiptsByResultID(ctx, resultID)
 	if err != nil {
 		return nil, err
 	}

As per coding guidelines, “Treat all inputs as potentially byzantine and classify errors in a context-dependent manner; no code path is safe unless explicitly proven and documented”.

Also applies to: 1149-1149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rpc/handler.go` at line 1120, Validate incoming request ID
payloads before calling convert.MessageToIdentifier: check req.GetBlockId() (and
the other ID used at the second call) for nil/empty and run explicit
validation/parse logic (e.g., base64/UUID/expected format decode) and return a
context-appropriate error if validation fails instead of passing malformed data
into convert.MessageToIdentifier; update the handler function around the uses of
convert.MessageToIdentifier to perform these checks and short-circuit with a
clear client-facing error (including which field failed) so invalid IDs are
rejected upstream rather than silently coerced.

Comment on lines +151 to +170
func ExecutionReceiptToMessage(receipt *flow.ExecutionReceipt, includeResult bool) (*entities.ExecutionReceipt, error) {
msg := &entities.ExecutionReceipt{
Meta: &entities.ExecutionReceiptMeta{
ExecutorId: IdentifierToMessage(receipt.ExecutorID),
ResultId: IdentifierToMessage(receipt.ExecutionResult.ID()),
Spocks: SignaturesToMessages(receipt.Spocks),
ExecutorSignature: MessageToSignature(receipt.ExecutorSignature),
},
}

if includeResult {
result, err := ExecutionResultToMessage(&receipt.ExecutionResult)
if err != nil {
return nil, fmt.Errorf("could not convert execution result: %w", err)
}
msg.ExecutionResult = result
}

return msg, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add nil guards to prevent panic on malformed receipt inputs.

On Line 154/Line 179 paths, a nil receipt will panic during field access. These converters should fail closed with explicit errors.

🛡️ Defensive conversion fix
 func ExecutionReceiptToMessage(receipt *flow.ExecutionReceipt, includeResult bool) (*entities.ExecutionReceipt, error) {
+	if receipt == nil {
+		return nil, fmt.Errorf("execution receipt is nil")
+	}
+
 	msg := &entities.ExecutionReceipt{
 		Meta: &entities.ExecutionReceiptMeta{
 			ExecutorId:        IdentifierToMessage(receipt.ExecutorID),
 			ResultId:          IdentifierToMessage(receipt.ExecutionResult.ID()),
 			Spocks:            SignaturesToMessages(receipt.Spocks),
 			ExecutorSignature: MessageToSignature(receipt.ExecutorSignature),
 		},
 	}
@@
 func ExecutionReceiptsToMessages(receipts []*flow.ExecutionReceipt, includeResult bool) ([]*entities.ExecutionReceipt, error) {
 	msgs := make([]*entities.ExecutionReceipt, len(receipts))
 	for i, receipt := range receipts {
+		if receipt == nil {
+			return nil, fmt.Errorf("execution receipt at index %d is nil", i)
+		}
 		msg, err := ExecutionReceiptToMessage(receipt, includeResult)
 		if err != nil {
 			return nil, fmt.Errorf("could not convert execution receipt at index %d: %w", i, err)
 		}
 		msgs[i] = msg
 	}
 	return msgs, nil
 }

As per coding guidelines "Treat all inputs as potentially byzantine and classify errors in a context-dependent manner; no code path is safe unless explicitly proven and documented".

Also applies to: 176-186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/common/rpc/convert/execution_results.go` around lines 151 - 170,
ExecutionReceiptToMessage currently dereferences receipt and its nested fields
(e.g., receipt.ExecutorID, receipt.ExecutionResult, receipt.Spocks,
receipt.ExecutorSignature) and can panic on nil inputs; add defensive nil guards
at the start of ExecutionReceiptToMessage to return a clear error if receipt is
nil and also verify receipt.ExecutionResult is non-nil before calling
ExecutionResultToMessage when includeResult is true (return a descriptive error
like "nil execution result in receipt"); ensure any sub-field dereferences
(ExecutorID, Spocks, ExecutorSignature) are safe or documented as required so
the function fails closed with explicit errors rather than panicking.

Comment on lines 73 to 81
s.localRest = map[string]struct{}{
"getBlocksByIDs": {},
"getBlocksByHeight": {},
"getBlockPayloadByID": {},
"getNetworkParameters": {},
"getNodeVersionInfo": {},
"getBlocksByIDs": {},
"getBlocksByHeight": {},
"getBlockPayloadByID": {},
"getNetworkParameters": {},
"getNodeVersionInfo": {},
"getExecutionReceiptsByBlockID": {},
"getExecutionReceiptsByResultID": {},
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if observer implements ReceiptsAPI methods locally or proxies them

# Search for observer-specific implementation of GetExecutionReceiptsByBlockID
echo "=== Searching for observer GetExecutionReceiptsByBlockID implementation ==="
rg -n -C5 'GetExecutionReceiptsByBlockID' --type go -g '*observer*'

# Check if there's a receipts backend or handler for observer
echo ""
echo "=== Searching for observer receipts handling ==="
rg -n -C3 'Receipts' --type go -g '*observer*'

Repository: onflow/flow-go

Length of output: 14438


🏁 Script executed:

#!/bin/bash
# Get the localRpc map definition and surrounding context
echo "=== localRpc map definition ==="
sed -n '61,71p' integration/tests/access/cohort2/observer_test.go

echo ""
echo "=== testedRPCs assignment and related context ==="
sed -n '83,95p' integration/tests/access/cohort2/observer_test.go

echo ""
echo "=== Check the RPC test structure to understand what s.testedRPCs contains ==="
sed -n '380,415p' integration/tests/access/cohort2/observer_test.go

Repository: onflow/flow-go

Length of output: 2857


🏁 Script executed:

#!/bin/bash
# Find the test logic that uses localRpc and localRest maps
echo "=== Search for how localRpc is used in test logic ==="
rg -n 'localRpc' integration/tests/access/cohort2/observer_test.go -A3 -B1

echo ""
echo "=== Search for how localRest is used in test logic ==="
rg -n 'localRest' integration/tests/access/cohort2/observer_test.go -A3 -B1

echo ""
echo "=== Search for HandledByObserver and HandledByUpstream logic ==="
rg -n 'HandledBy|isLocalEndpoint' integration/tests/access/cohort2/observer_test.go -B2 -A2

Repository: onflow/flow-go

Length of output: 4276


Add execution receipt RPC methods to localRpc map for consistency with REST endpoints.

The new execution receipt endpoints are handled differently between REST and RPC:

  • REST endpoints getExecutionReceiptsByBlockID and getExecutionReceiptsByResultID are in localRest (lines 79-80)
  • RPC methods GetExecutionReceiptsByBlockID and GetExecutionReceiptsByResultID are NOT in localRpc (lines 61-71)

This means REST endpoints will be tested as locally handled (HandledByObserver test), while RPC methods will be tested as proxied to upstream (HandledByUpstream test). Since the observer has ExecutionReceipts storage available (cmd/observer/node_builder/observer_builder.go:2130), both should be handled consistently. Add the RPC methods to localRpc to match the REST behavior, unless there's a specific reason these should be proxied differently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integration/tests/access/cohort2/observer_test.go` around lines 73 - 81, The
localRpc map is missing the RPC method names for execution receipts, causing
inconsistent handling vs localRest; update the localRpc initialization (the map
named localRpc) to include "GetExecutionReceiptsByBlockID" and
"GetExecutionReceiptsByResultID" (exact strings) so RPC calls are treated as
locally handled by the observer like the REST endpoints (which contain
getExecutionReceiptsByBlockID/getExecutionReceiptsByResultID); ensure you add
those two keys to the same map literal where other RPC methods are listed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/access/rpc/backend/backend_execution_results.go`:
- Line 61: The NotFound error messages currently interpolate the storage error
variable `err` instead of the caller-supplied identifiers — change the
status.Errorf calls in backend_execution_results.go to include the requested
identifier (use `blockID` for the execution receipts path and `resultID` for the
other path) in the NotFound message so the client sees which ID was not found;
keep `err` available for internal logs if needed but ensure the user-facing
message prints the requested ID rather than `err`.
- Around line 94-100: The current loop filters allReceipts for ones whose
receipt.ExecutionResult.ID() equals resultID but returns an empty slice with nil
error when none match; change the function to check after the loop if
len(receipts) == 0 and return a NotFound error instead of (receipts, nil) so
callers can distinguish “no receipts found” from a valid empty result. Locate
the block that iterates over allReceipts and replace the final return with a
conditional that returns the collected receipts when non-empty and an
appropriate NotFound error when empty (referencing allReceipts,
receipt.ExecutionResult.ID(), and resultID to find the code).

In `@integration/tests/access/cohort2/observer_indexer_enabled_test.go`:
- Around line 464-488: The test uses converted.Payload.Results[0].ID() which can
drift; instead call GetExecutionReceiptsByBlockID via the existing checkRPC flow
to obtain actual receipts, assert the block lookup returns at least one receipt,
extract receipt.Meta.ResultId from the first receipt and use
convert.IdentifierToMessage(receipt.Meta.ResultId) for the
GetExecutionReceiptsByResultID request, then assert the returned receipts are
non-empty and that each returned receipt.Meta.ResultId equals the seed result
id; update the code around the GetExecutionReceiptsByBlockID /
GetExecutionReceiptsByResultID calls (referencing checkRPC,
GetExecutionReceiptsByBlockID, GetExecutionReceiptsByResultID,
receipt.Meta.ResultId, convert.IdentifierToMessage) accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6919e698-4e58-4308-94fe-02b2000b65aa

📥 Commits

Reviewing files that changed from the base of the PR and between 5ee6dee and d918cc0.

📒 Files selected for processing (3)
  • engine/access/apiproxy/access_api_proxy.go
  • engine/access/rpc/backend/backend_execution_results.go
  • integration/tests/access/cohort2/observer_indexer_enabled_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • engine/access/apiproxy/access_api_proxy.go

irrecoverable.Throw(ctx, err)
return nil, err
}
return nil, status.Errorf(codes.NotFound, "could not find execution receipts for block: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the requested identifier in the NotFound message.

Both paths interpolate err instead of the ID the caller supplied, so the client loses the failing blockID/resultID and just sees the storage sentinel text.

Suggested patch
-		return nil, status.Errorf(codes.NotFound, "could not find execution receipts for block: %v", err)
+		return nil, status.Errorf(codes.NotFound, "could not find execution receipts for block: %x", blockID)
-		return nil, status.Errorf(codes.NotFound, "could not find execution result: %v", err)
+		return nil, status.Errorf(codes.NotFound, "could not find execution result: %x", resultID)

Also applies to: 80-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@engine/access/rpc/backend/backend_execution_results.go` at line 61, The
NotFound error messages currently interpolate the storage error variable `err`
instead of the caller-supplied identifiers — change the status.Errorf calls in
backend_execution_results.go to include the requested identifier (use `blockID`
for the execution receipts path and `resultID` for the other path) in the
NotFound message so the client sees which ID was not found; keep `err` available
for internal logs if needed but ensure the user-facing message prints the
requested ID rather than `err`.

Comment on lines +464 to +488
// GetExecutionReceiptsByBlockID
checkRPC(func(client accessproto.AccessAPIClient) (any, error) {
res, err := client.GetExecutionReceiptsByBlockID(ctx, &accessproto.GetExecutionReceiptsByBlockIDRequest{
BlockId: blockWithAccount.Block.Id,
})
if err != nil {
return nil, err
}
return res.Receipts, nil
})

// GetExecutionReceiptsByResultID
checkRPC(func(client accessproto.AccessAPIClient) (any, error) {
converted, err := convert.MessageToBlock(blockWithAccount.Block)
require.NoError(t, err)

resultId := converted.Payload.Results[0].ID()
res, err := client.GetExecutionReceiptsByResultID(ctx, &accessproto.GetExecutionReceiptsByResultIDRequest{
ResultId: convert.IdentifierToMessage(resultId),
})
if err != nil {
return nil, err
}
return res.Receipts, nil
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use a receipt-derived result ID for the happy-path receipt test.

converted.Payload.Results[0].ID() is not a reliable stand-in here; integration/tests/access/cohort1/access_api_test.go:1388-1410 explicitly reads receipt.Meta.ResultId to avoid proto round-trip hash drift. With the current checkRPC helper only comparing equality, all three clients can return the same empty slice and this test still passes. Seed GetExecutionReceiptsByResultID from a receipt returned by GetExecutionReceiptsByBlockID, assert that the block lookup returns at least one receipt, and then verify the returned receipts all commit to that result ID.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@integration/tests/access/cohort2/observer_indexer_enabled_test.go` around
lines 464 - 488, The test uses converted.Payload.Results[0].ID() which can
drift; instead call GetExecutionReceiptsByBlockID via the existing checkRPC flow
to obtain actual receipts, assert the block lookup returns at least one receipt,
extract receipt.Meta.ResultId from the first receipt and use
convert.IdentifierToMessage(receipt.Meta.ResultId) for the
GetExecutionReceiptsByResultID request, then assert the returned receipts are
non-empty and that each returned receipt.Meta.ResultId equals the seed result
id; update the code around the GetExecutionReceiptsByBlockID /
GetExecutionReceiptsByResultID calls (referencing checkRPC,
GetExecutionReceiptsByBlockID, GetExecutionReceiptsByResultID,
receipt.Meta.ResultId, convert.IdentifierToMessage) accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@engine/access/rpc/backend/backend_execution_results.go`:
- Around line 102-104: The error string returned when len(receipts) == 0 is
incorrect; update the status.Errorf call in the function that checks receipts
(the block handling variable receipts in backend_execution_results.go) to use
"no receipts found for result" (or similar wording referencing receipts) instead
of "no results found for result" so the message matches the method's purpose and
variable names.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fdc708e2-cc19-47f8-9fb0-67d89a803164

📥 Commits

Reviewing files that changed from the base of the PR and between 30b7273 and eb6e5ec.

📒 Files selected for processing (3)
  • engine/access/rpc/backend/backend_execution_results.go
  • engine/access/rpc/backend/backend_execution_results_test.go
  • storage/receipts.go
✅ Files skipped from review due to trivial changes (1)
  • storage/receipts.go

peterargue and others added 3 commits March 6, 2026 10:55
Co-authored-by: Tim Barry <21149133+tim-barry@users.noreply.github.com>
// Expected error returns during normal operation:
// - [codes.NotFound]: if no receipts are indexed for the given block ID.
func (b *backendExecutionResults) GetExecutionReceiptsByBlockID(ctx context.Context, blockID flow.Identifier) ([]*flow.ExecutionReceipt, error) {
receipts, err := b.receipts.ByBlockID(blockID)
Copy link
Member

@zhangchiqing zhangchiqing Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add comments about who builds this index (blockID -> receiptID)?

I think they are from the AN's ingestion engine indexing the receipts. right?

I was thinking to deprecate that in the collection syncing POC PR, because AN can see all receipts by following the block.

If I do deprecate them in the ingestion engine, then I was thinking to create a worker that subscribes BlockProcessed events, and read the payload and stores the receipts. That way EN no longer need to send receipts to AN or VN, but only SN.

Not something to change in this PR, but just thought about it and want to bring this up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That index is built whenever a receipt is stored, which is done explicit by ANs when they receive a receipt from ENs, but also by the follower when persisting blocks.

I'll add a comment.

@peterargue peterargue requested a review from tim-barry March 7, 2026 01:51
Co-authored-by: Tim Barry <21149133+tim-barry@users.noreply.github.com>
@peterargue peterargue enabled auto-merge March 9, 2026 22:52
@peterargue peterargue disabled auto-merge March 9, 2026 22:52
@peterargue peterargue added this pull request to the merge queue Mar 9, 2026
Merged via the queue into master with commit 6181fa2 Mar 9, 2026
61 checks passed
@peterargue peterargue deleted the peter/add-execution-receipt-access branch March 9, 2026 23:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants