erigontech / erigontech/erigon
rpc: eth_subscribe("transactionReceipts") is not compatible with geth
- Dominant language
- Go
- Stars
- 3.6k
- Forks
- 1.5k
- Avg merge
- 1d 16h
- Merged PRs (30d)
- 455
Description
`eth_subscribe("transactionReceipts")` does not send what geth sends. References: erigon `main` at 25ac06305d, go-ethereum `master` at b63e2c0503.
Geth builds every notification with `ethapi.MarshalReceipt`, the same object as `eth_getTransactionReceipt`, and sends all receipts matched in a block as one array (`eth/filters/api.go`, `FilterAPI.TransactionReceipts`).
## Differences
**Logs have 4 fields instead of 10.** `MarshalSubscribeReceipt` (`execution/types/ethutils/receipt.go:143`) writes only `address`, `topics`, `data` and `transactionHash` for each log. Geth's `types.Log` also has `blockNumber`, `transactionIndex`, `blockHash`, `blockTimestamp`, `logIndex` and `removed`. The backend already sends all of them in `SubscribeLogsReply` (`node/privateapi/logsfilter.go`, `logNotificationToProto`), so this is an RPC-side change.
**`effectiveGasPrice` is the block base fee.** `receipt.go:212` sets it from `protoReceipt.BaseFee`, the header base fee (`node/privateapi/receiptsfilter.go:229`). For a transaction that pays a priority fee the real value is the base fee plus the tip, so the field is too low. `SubscribeReceiptsReply` does not carry the fee cap or the tip, so the fix needs the backend to send the effective gas price (a new field in `erigontech/interfaces`) or to compute it before sending.
**One notification per receipt.** `rpc/jsonrpc/eth_filters.go:282` emits `[]map[string]any{receipt}`, an array of one, per receipt. Geth sends one array per block with every matched receipt.
**Possible nil dereference (not reproduced).** The backend recovers the sender with `types.MakeSigner(nil, blockNum, 0)` (`receiptsfilter.go:216`), which returns a zero `Signer` without chain ID or forks (`execution/types/transaction_signing.go:43`). That works only while the transaction already caches its sender; a zero signer cannot recover a typed transaction. When recovery fails, or `rn.Tx` is nil, `From` stays nil, and `MarshalSubscribeReceipt` passes it to `gointerfaces.ConvertH160toAddress` (`receipt.go:155`), which dereferences it. The call runs in the subscription goroutine without a recover. Whether receipts can reach this path without a cached sender still needs a test.
## Proposed fix
- RPC side: complete logs, one notification per block, and a test that feeds a reply without `From`.
- Backend: build the signer from the chain config, and send the effective gas price.
**`eth_sendRawTransactionSync` has the same problem.** EIP-7966 says it returns the same object as `eth_getTransactionReceipt`, but its subscription path (`rpc/jsonrpc/send_transaction.go`) returns `MarshalSubscribeReceipt`: logs with 4 fields, the base fee as `effectiveGasPrice`, and no `blobGasPrice`, because the backend never sets `SubscribeReceiptsReply.BlobGasPrice`.
Contributor guide
Assessment
This issue has not been assessed yet.