Skip to content

feat: eth_getTransactionReceipt and eth_getBlockReceipts RPCs#150

Draft
JonathanOppenheimer wants to merge 32 commits intoarr4n/recoveryfrom
JonathanOppenheimer/support-reciepts
Draft

feat: eth_getTransactionReceipt and eth_getBlockReceipts RPCs#150
JonathanOppenheimer wants to merge 32 commits intoarr4n/recoveryfrom
JonathanOppenheimer/support-reciepts

Conversation

@JonathanOppenheimer
Copy link
Member

@JonathanOppenheimer JonathanOppenheimer commented Feb 5, 2026

Closes #91

This PR adds receipt retrieval for eth_getTransactionReceipt and eth_getBlockReceipts. To do so, it adds the following backend methods:

  • GetReceipts(ctx, hash) - Cache-first receipt lookup (checks vm.blocks, falls back to DB)
  • BlockByNumberOrHash(ctx, blockNrOrHash) - Unified block lookup
    • Taken directly from the devnet
  • HeaderByNumberOrHash(ctx, blockNrOrHash) - Unified header lookup
    • Taken directly from the devnet
  • readByNumberOrHash[T]() - Generic helper for number-or-hash lookups
    • Taken directly from the devnet

necessary to support these RPCs. I also added a (fairly comprehensive test), TestReceiptAPIs which covers:

  • Cache path (executed, not yet settled) and DB path (settled, evicted from cache)
  • Cache-vs-DB equivalence via cmp.Diff with cmputils.Receipts()
    • I also tried to write a test to ensure the base fee changes would be correct, but was unable to write a test simulating a full execcution cycle where the base fee changed and was not the worst case.
  • Multi-transaction blocks (correct TransactionIndex ordering)
  • Pending/unexecuted blocks return nil
  • Nonexistent hashes return nil
  • eth_getBlockReceipts by hash and by number produce equivalent results
  • Genesis block returns empty receipts

While working on this PR, I surfaced some inconsistencies about the fields of receipts, which I fixed in execution.go. See comments below.

@JonathanOppenheimer JonathanOppenheimer changed the title feat: reciepts RPCs feat: eth_getTransactionReceipt and eth_getBlockReceipts RPCs Feb 5, 2026
@JonathanOppenheimer JonathanOppenheimer self-assigned this Feb 5, 2026
Comment on lines 185 to 196
// The [types.Header] that we pass to [core.ApplyTransaction] is adjusted to
// reduce worst‑case gas, which changes the block hash. DeriveFields recomputes
// receipt/log metadata (e.g., block hash, effective gas price, etc.) against the final
// block header so cached receipts match the DB path.
var blobGasPrice *big.Int
if header.ExcessBlobGas != nil {
blobGasPrice = eip4844.CalcBlobFee(*header.ExcessBlobGas)
}
if err := receipts.DeriveFields(e.chainConfig, b.Hash(), b.NumberU64(), header.Time, header.BaseFee, blobGasPrice, b.Transactions()); err != nil {
return err
}

Copy link
Member Author

@JonathanOppenheimer JonathanOppenheimer Feb 5, 2026

Choose a reason for hiding this comment

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

This was surfaced after writing tests that compare the receipts of cached blocks vs settled blocks, and we found that the effective gas price was missing from the receipts of blocks in the cache, but not from those of the database. There was an inconsistency here with how receipts are provided from the database and manually resetting the block hash pointed to that.

In geth, receipts are only retrieved from settled blocks, which have all their fields properly derived via DeriveFields(). We match that behavior here.

Thanks to @alarso16 for helping me figure this out.


func TestReceiptAPIs(t *testing.T) {
opt, vmTime := withVMTime(t, time.Unix(saeparams.TauSeconds, 0))
ctx, sut := newSUT(t, 5, opt)
Copy link
Member Author

Choose a reason for hiding this comment

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

Creating the sut here is acceptable from my understanding. We create and use 5 accounts to avoid logging logging nonce stuff.

@JonathanOppenheimer JonathanOppenheimer changed the base branch from main to arr4n/recovery February 6, 2026 00:39
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from b28849b to 13c89e4 Compare February 6, 2026 00:52
@ARR4N
Copy link
Collaborator

ARR4N commented Feb 6, 2026

The arr4n/recovery branch is a WIP that doesn't have a non-draft PR associated (i.e. I reserve the right to force-push to it) so your workflow is going to be regularly broken.

@JonathanOppenheimer
Copy link
Member Author

The arr4n/recovery branch is a WIP that doesn't have a non-draft PR associated (i.e. I reserve the right to force-push to it) so your workflow is going to be regularly broken.

Yup all good, I'm going to leave this as a draft until your base PR is merged

JonathanOppenheimer and others added 2 commits February 6, 2026 12:11
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch 2 times, most recently from 26be9e3 to c82e54e Compare February 6, 2026 17:24
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from c82e54e to ac595dc Compare February 6, 2026 17:25
JonathanOppenheimer and others added 2 commits February 10, 2026 11:26
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
sae/rpc_test.go Outdated
blockMultiTxs := sut.createAndAcceptBlock(t, txMulti1, txMulti2, txMulti3)
require.NoErrorf(t, blockMultiTxs.WaitUntilExecuted(ctx), "%T.WaitUntilExecuted()", blockMultiTxs)

// Block 4: Triggers settlement of block 2
Copy link
Contributor

Choose a reason for hiding this comment

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

I think block 3 settled block 4

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't call WaitUntilSettled until here.

sae/rpc_test.go Outdated

var receipt *types.Receipt
err := sut.CallContext(ctx, &receipt, "eth_getTransactionReceipt", txSettled.Hash())
require.ErrorContains(t, err, "reading execution-result base fee")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use require.ErrorIs?

Copy link
Member Author

Choose a reason for hiding this comment

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

We need Tsvetan's PR to clean this up.

@ARR4N ARR4N force-pushed the arr4n/recovery branch 4 times, most recently from ca81fd1 to 433de16 Compare February 11, 2026 14:51
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch 2 times, most recently from 9129673 to bc709d1 Compare February 11, 2026 16:40
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from 512ee0f to 6774ac3 Compare February 11, 2026 16:43
// modified to reduce gas price from the worst-case value agreed by
// consensus. This changes the hash, which is what is copied to receipts
// and logs.
receipt.BlockHash = b.Hash()
Copy link
Member Author

Choose a reason for hiding this comment

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

The below block does this -- confirmed with Cey that we can remove this.

Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from 36787bf to 9e046d4 Compare February 11, 2026 19:56
JonathanOppenheimer and others added 2 commits February 11, 2026 15:48
Co-authored-by: Austin Larson <78000745+alarso16@users.noreply.github.com>
Signed-off-by: Jonathan Oppenheimer <147infiniti@gmail.com>
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from eef7035 to 1baee20 Compare February 11, 2026 21:00
@JonathanOppenheimer JonathanOppenheimer force-pushed the JonathanOppenheimer/support-reciepts branch from 1baee20 to 333e8ae Compare February 11, 2026 21:01
@@ -316,6 +328,23 @@ func readByHash[T any](b *ethAPIBackend, hash common.Hash, read canonicalReader[

var errFutureBlockNotResolved = errors.New("not accepted yet")
Copy link
Member Author

Choose a reason for hiding this comment

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

Why is this variable declared here. I'd move it but it would create an irrelevant diff. Seems very strange to declare an error after it is used.

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.

API Support - Receipt retrieval

3 participants