rpc: impl eth fill transaction#21870
Conversation
SignTransactionResult.MarshalJSON now explicitly emits gasPrice:null for EIP-1559 transactions, maxFeePerGas:null/maxPriorityFeePerGas:null for legacy transactions, and normalises null v/r/s to "0x0" for unsigned transactions. Adds blob/authorizationList validation checks to FillTransaction and two unit tests that pin the serialisation contract.
fillFeeDefaults constructed NewOracle unconditionally, wasting allocs on the three fast-exit paths (pre-London with gasPrice set, post-London with all EIP-1559 fields supplied, post-London legacy gasPrice path). Move oracle construction to the two branches that actually call SuggestTipCap. Also remove the redundant isPostLondon variable.
There was a problem hiding this comment.
Pull request overview
Implements the eth_fillTransaction JSON-RPC method in rpcdaemon, returning an unsigned, default-populated transaction along with its raw-encoded form (Geth-compatible response shape).
Changes:
- Add
eth_fillTransactionimplementation that fills nonce/fees/gas/chainId defaults and returns an RLP/binary-encoded transaction plus its JSON form. - Introduce
ethapi.SignTransactionResultwith custom JSON marshaling to match Geth’s field behavior for unsigned txs. - Update docs and CI rpc-tests version pinning to reflect the new method support.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| rpc/jsonrpc/eth_fill_transaction.go | Adds the FillTransaction RPC implementation and fee-default helper logic. |
| rpc/jsonrpc/eth_fill_transaction_test.go | Adds basic unit tests for default filling and a few validation errors. |
| rpc/jsonrpc/eth_api.go | Extends the EthAPI interface to include FillTransaction. |
| rpc/ethapi/api.go | Adds SignTransactionResult + custom MarshalJSON to normalize response fields for fill/sign flows. |
| rpc/ethapi/api_test.go | Adds tests covering SignTransactionResult JSON normalization for legacy and EIP-1559 txs. |
| cmd/rpcdaemon/README.md | Marks eth_fillTransaction as implemented in the RPC support table. |
| .github/workflows/scripts/rpc_version.env | Changes the pinned rpc-tests version/branch used by CI workflows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Propagate GetTransactionCount error in nonce fallback instead of silently leaving nonce at 0, which would produce an incorrect contract address for deployments. - Return a clear error for blob transactions on pre-Cancun chains (ExcessBlobGas == nil) instead of panicking when ToTransaction dereferences a nil MaxFeePerBlobGas.
- validate maxFeePerBlobGas != 0 if specified (matches Geth) - extract newGasOracle helper to avoid duplicating oracle construction in fillFeeDefaults - add tests for From=nil (nonce defaults to 0), explicit nonce preservation, explicit gas preservation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- preserve user-provided gas (pass 0 instead of api.GasCap to ToTransaction) - warn on txpool nonce fallback instead of silently using on-chain nonce - improve error message when only maxFeePerGas is provided and oracle tip exceeds it - add README note about blob sidecar not supported - add tests for pool error fallback, gas cap bypass, pool nonce, fee filling, gas price post-London - avoid calling gas oracle when MaxPriorityFeePerGas is already set - merge blob-hash guards into single block; drop redundant proto nil guard; inline baseFee
yperbasis
left a comment
There was a problem hiding this comment.
Requesting changes — mostly mechanical, plus two items that need a decision. Implementation itself looks correct (builds clean, new tests pass, JSON shape matches Geth's types.Transaction output).
Blocking
.github/workflows/scripts/rpc_version.envis pinned to a feature branch (lupin012/add_test_eth_fillTransaction) and adds a stray trailing newline. Branch pins make CI non-reproducible and break if the branch moves — please cut a tag inrpc-testsand pin to it before merge (as the PR description already plans).- The branch currently conflicts with
main(needs a rebase/merge).
Please address or reply
gasPrice+accessListpost-London: the sharedToTransactionreturns an AccessListTx (type 1) where Geth forces LegacyTx (type 0). Sahil-4555's thread on this is still open — please make an explicit call and document the divergence. (Note Geth's path drops the access list, so blindly matching it isn't clearly better.)- The nonce-filling block in
eth_fill_transaction.gore-implements whatGetTransactionCountalready does forpending(rpc/jsonrpc/eth_accounts.go:99-111: txpool nonce+1 → state fallback). Suggest reusing it — simpler, and it propagates the pool error like Geth instead of silently falling back to the on-chain nonce.
Minor / non-blocking
SignTransactionResult.MarshalJSON: the marshal → map → strip → re-marshal denylist will silently leak any future block-placement field added toRPCTransactioninto the output. A dedicated typed output struct would be more robust.rawfield:ethereum/execution-apis#803drops it fromFillTransactionResult. Keeping it to match current Geth is fine, but it's a known future breaking change for a new method — worth linking a tracking issue.
… type-1 divergence Two fixes for yperbasis's review of PR #21870: 1. gasPrice + accessList type: document the deliberate divergence from Geth in ToTransaction — Erigon preserves type-1 when an explicit accessList is given alongside gasPrice; Geth silently drops it to LegacyTx (type 0). Add TestFillTransactionGasPriceWithAccessListIsTypeOne to pin the behavior. 2. Nonce filling: replace the inline txpool→warn→fallback block with a single GetTransactionCount(pending) call. Propagates pool errors to the caller instead of silently falling back to the on-chain nonce, matching Geth's behavior. Also fix _GetBlockNumber to guard LastPendingBlock behind a nil check on filters (consistent with the existing nil guard for WithOverlay).
…yped allowlist Replace the marshal→map→strip→re-marshal approach with a dedicated signedTxJSON struct. New fields added to RPCTransaction will not leak into the output since only explicitly listed fields are marshalled.
|
@yperbasis |
yperbasis
left a comment
There was a problem hiding this comment.
Thanks for this. A few correctness and test-quality issues to sort out before it can go in — details inline.
…Transaction - Hoist head.ExcessBlobGas == nil check outside MaxFeePerBlobGas == nil guard so pre-Cancun blob requests error even when maxFeePerBlobGas is explicit. - Fix TestFillTransactionExplicitNoncePreserved: use nonce 7 (not 42, which collides with the DB state of the test sender and made the test vacuous). - Fix TestFillTransactionUserGasAboveCapPreserved comment: FillTransaction passes globalGasCap=0 so no capping can occur on that path. - Add nil guard on r.Tx in SignTransactionResult.MarshalJSON to prevent panic when the type is used with a nil transaction. - Add TestFillTransactionBlobPreCancunExplicitBlobFee and TestSignTransactionResultMarshalJSON_NilTx.
yperbasis
left a comment
There was a problem hiding this comment.
A few changes before merge:
-
signedTxJSON(rpc/ethapi/api.go) duplicatesRPCTransactionand diverges from Geth's output shape. It's a hand-maintained parallel copy of the fields — ifRPCTransactionever gains a field,eth_fillTransactionsilently drops it (no compile error, no test failure). It also dropsomitemptyfromgasPrice/maxFeePerGas/maxPriorityFeePerGas, so a 1559 tx emits"gasPrice": nulland a legacy tx emits"maxFeePerGas": null/"maxPriorityFeePerGas": null. Geth instead omits the inapplicable 1559 fields on a legacy tx and setsgasPriceto the fee cap on an unmined 1559 tx (herecomputeGasPricereturns nil becauseFillTransactioncallsNewRPCTransactionwithbaseFee == nil). Please confirm the reference rpc-tests expect this exact shape, or marshalRPCTransactionand strip the unwanted keys so new fields flow through automatically. -
FillTransactionestimates gas againstlatestbut takes the nonce frompending.EstimateGas(ctx, &args, nil, …)defaults to the latest block, while the nonce comes fromGetTransactionCount(…, PendingBlockNumber). For a sender with in-flight pending txs the estimate can be wrong or fail even though the tx would succeed once those land — the two should use the same block. -
newGasOracleduplicates oracle construction already written verbatim three times ineth_system.go(GasPrice,MaxPriorityFeePerGas,FeeHistory). Route those call sites through the new helper so the wiring lives in one place and can't drift. -
Comment policy (
.claude/rules/comments.md): the "Deliberate divergence from Geth …" comment onToTransactionis PR-description narration and is repeated verbatim in the test — trim it to one sentence and drop the test copy. -
Nit: the new files carry
// Copyright 2024 The Erigon Authors— should be 2026.
…Transaction - Remove signedTxJSON; marshal RPCTransaction via map and strip placement/sender fields so new fields flow through automatically; inapplicable fee fields are absent (not null), matching Geth - Estimate gas against explicit latest block reference (matching Geth) - Consolidate newGasOracle helper; route GasPrice/MaxPriorityFeePerGas/FeeHistory through it with feeHistoryCache wired in - Fix MaxFeePerBlobGas default: use CalcExcessBlobGas for next-block excess instead of head.ExcessBlobGas directly - Add TestFillTransactionBlobFeeUsesNextBlockExcess (RED before fix, GREEN after)
…nResult EIP-1559 txs lack gasPrice; legacy txs lack maxFeePerGas/maxPriorityFeePerGas. RPCTransaction omits nil fields, but Geth emits null for them. Inject null in SignTransactionResult.MarshalJSON to match Geth's eth_fillTransaction output.
yperbasis
left a comment
There was a problem hiding this comment.
Approving — implementation is solid and well-tested.
One thing worth confirming before tagging the rpc-tests version: the blob fee default in eth_fill_transaction.go diverges from Geth in two ways. Erigon computes maxFeePerBlobGas as 2 × GetBlobGasPrice(CalcExcessBlobGas(next block)) — basing it on the next block's excess blob gas and doubling the result. Geth's setFeeDefaults uses eip4844.CalcBlobFee(config, head) on the head block with no doubling (it only doubles the regular maxFeePerGas). Since the new rpc-tests compare against Geth's response, a blob-fill case would return a larger value and mismatch. TestFillTransactionBlobFeeUsesNextBlockExcess only pins Erigon's own chosen value, so it won't catch this. If the divergence is intentional, a note in the README entry would help.
…directly; keep 2× fee headroom on maxFeePerBlobGas as Geth does
|
@yperbasis: aligned blob fee default to Geth:
|
closes #21864 Implements eth_fillTransaction compatible with Geth. The method accepts the parameters of a partial transaction, fills in the missing fields (nonce, gas limit, gas price / EIP-1559 fee caps, chain ID, blob fee), and returns the RLP-serialized transaction (raw) together with its JSON representation (tx), without signing or submitting it. Main changes - rpc/jsonrpc/eth_fill_transaction.go — implementation of FillTransaction on APIImpl, with a fillFeeDefaults helper that handles both the pre-London path (gas price from oracle) and the post-London path (EIP-1559, blob fee). - rpc/ethapi/api.go — adds SignTransactionResult (reusable by eth_signTransaction as well) with a custom MarshalJSON that strips block-placement/sender fields and normalises fee and signature fields to match Geth's output. - rpc/jsonrpc/eth_api.go — adds FillTransaction to the EthAPI interface. - rpc/jsonrpc/eth_fill_transaction_test.go and rpc/ethapi/api_test.go — tests covering the happy path, conflicting fee arguments, wrong chainID, contract creation without data, blob transactions before Cancun, and JSON serialisation for both EIP-1559 and legacy transactions. - cmd/rpcdaemon/README.md — marks eth_fillTransaction as implemented. Notes * The rpc-tests version will be tagged and rpc_version.env updated to a stable tag once the PR is approved * Added 15 RPC tests to latest, comparing them against the Geth response. --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
closes #21864
Implements eth_fillTransaction compatible with Geth.
The method accepts the parameters of a partial transaction, fills in the missing fields (nonce, gas limit, gas price / EIP-1559 fee caps, chain ID, blob fee), and returns the RLP-serialized transaction (raw) together with its JSON representation (tx), without signing or submitting it.
Main changes
Notes