Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9d99fc3
impl eth_fillTransaction()
lupin012 Jun 17, 2026
5badd30
dave temporray rpc branch
lupin012 Jun 17, 2026
f43f0aa
rpc/ethapi, rpc/jsonrpc: align eth_fillTransaction JSON format with Geth
lupin012 Jun 17, 2026
7b11cb3
rpc/jsonrpc: defer oracle construction to branches that use it
lupin012 Jun 17, 2026
8e2659a
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 17, 2026
7b9413f
rpc/jsonrpc: fix two bugs in FillTransaction found in PR review
lupin012 Jun 18, 2026
dab8498
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 18, 2026
a24830c
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 20, 2026
4f26a44
rpc: eth_fillTransaction review fixes
lupin012 Jun 20, 2026
5f41116
rpc: eth_fillTransaction review fixes and simplifications
lupin012 Jun 21, 2026
b61307e
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 21, 2026
0b8887b
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 22, 2026
1aab299
rpc/ethapi, rpc/jsonrpc, rpc/rpchelper: eth_fillTransaction nonce and…
lupin012 Jun 22, 2026
51895b6
rpc/ethapi: replace SignTransactionResult.MarshalJSON denylist with t…
lupin012 Jun 22, 2026
39bb6a8
rpc/ethapi, rpc/jsonrpc: address yperbasis round-2 review on eth_fill…
lupin012 Jun 23, 2026
6ee6ee1
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 23, 2026
29c1cb4
rpc/ethapi, rpc/jsonrpc: address yperbasis round-3 review on eth_fill…
lupin012 Jun 24, 2026
902178d
rpc/ethapi: inject null for inapplicable fee fields in SignTransactio…
lupin012 Jun 24, 2026
081cf78
update tag rpc version
lupin012 Jun 26, 2026
760acdb
rpc/jsonrpc: align blob fee default to Geth — use head ExcessBlobGas …
lupin012 Jun 26, 2026
3f38292
Merge branch 'main' into lupin012/impl_eth_fillTransaction
lupin012 Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/scripts/rpc_version.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
RPC_VERSION=v2.17.0
RPC_VERSION=v2.18.0


1 change: 1 addition & 0 deletions cmd/rpcdaemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ The following table shows the current implementation status of Erigon's RPC daem
| eth_sendTransaction | - | not yet implemented |
| eth_sign | No | deprecated |
| eth_signTransaction | - | not yet implemented |
| eth_fillTransaction | Yes | Blob sidecar generation (KZG commitments/proofs from raw blobs) not yet supported |
| | | |
| eth_getProof | Yes | Limited to last 100000 blocks |
| | | |
Expand Down
46 changes: 46 additions & 0 deletions rpc/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package ethapi

import (
"encoding/json"
"errors"
"fmt"
"math/big"
Expand Down Expand Up @@ -286,6 +287,7 @@ func (args *CallArgs) ToTransaction(globalGasCap uint64, baseFee *uint256.Int) (
TipCap: *msg.TipCap(),
AccessList: al,
}
// Unlike Geth, an explicit accessList with gasPrice produces type 1 rather than dropping the list.
case args.AccessList != nil:
al := types.AccessList{}
if args.AccessList != nil {
Expand Down Expand Up @@ -495,6 +497,50 @@ func RPCMarshalBlockExDeprecated(block *types.Block, inclTx bool, fullTx bool, b
return fields, nil
}

// SignTransactionResult represents a RLP-encoded transaction paired with its JSON form.
type SignTransactionResult struct {
Raw hexutil.Bytes `json:"raw"`
Tx *RPCTransaction `json:"tx"`
}
Comment thread
lupin012 marked this conversation as resolved.

func (r SignTransactionResult) MarshalJSON() ([]byte, error) {
if r.Tx == nil {
return nil, errors.New("nil transaction")
}
type plain struct {
Raw hexutil.Bytes `json:"raw"`
Tx json.RawMessage `json:"tx"`
}
txBytes, err := json.Marshal(r.Tx)
if err != nil {
return nil, err
}
var m map[string]json.RawMessage
if err := json.Unmarshal(txBytes, &m); err != nil {
return nil, err
}
for _, k := range []string{"blockHash", "blockNumber", "blockTimestamp", "transactionIndex", "from"} {
delete(m, k)
}
nullVal := json.RawMessage("null")
for _, k := range []string{"gasPrice", "maxFeePerGas", "maxPriorityFeePerGas"} {
if _, ok := m[k]; !ok {
m[k] = nullVal
}
}
zeroHex := json.RawMessage(`"0x0"`)
for _, k := range []string{"v", "r", "s"} {
if v, ok := m[k]; !ok || string(v) == "null" {
m[k] = zeroHex
}
}
stripped, err := json.Marshal(m)
if err != nil {
return nil, err
}
return json.Marshal(plain{Raw: r.Raw, Tx: stripped})
}

// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
type RPCTransaction struct {
BlockHash *common.Hash `json:"blockHash"`
Expand Down
88 changes: 88 additions & 0 deletions rpc/ethapi/api_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package ethapi

import (
"bytes"
"encoding/json"
"testing"

"github.com/holiman/uint256"
Expand Down Expand Up @@ -88,6 +90,92 @@ func TestNewRPCTransaction_EIP1559_AllZeroSig(t *testing.T) {
require.EqualValues(t, 0, result.S.ToInt().Int64())
}

func txFields(t *testing.T, r SignTransactionResult) map[string]json.RawMessage {
t.Helper()
data, err := json.Marshal(r)
require.NoError(t, err)
var outer struct {
Tx json.RawMessage `json:"tx"`
}
require.NoError(t, json.Unmarshal(data, &outer))
var fields map[string]json.RawMessage
require.NoError(t, json.Unmarshal(outer.Tx, &fields))
return fields
}

func TestSignTransactionResultMarshalJSON_EIP1559(t *testing.T) {
to := common.HexToAddress("0x1234567890123456789012345678901234567890")
chainID := uint256.NewInt(1)
tx := &types.DynamicFeeTransaction{
CommonTx: types.CommonTx{
Nonce: 1,
GasLimit: 21000,
To: &to,
},
ChainID: *chainID,
TipCap: *uint256.NewInt(1e9),
FeeCap: *uint256.NewInt(2e9),
}
var buf bytes.Buffer
require.NoError(t, tx.MarshalBinary(&buf))

fields := txFields(t, SignTransactionResult{Raw: buf.Bytes(), Tx: NewRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil)})

// EIP-1559 without known baseFee: gasPrice is null (matching Geth).
require.Equal(t, "null", string(fields["gasPrice"]), "gasPrice must be null when baseFee is unknown")

// maxFeePerGas and maxPriorityFeePerGas must be set.
require.NotEqual(t, "null", string(fields["maxFeePerGas"]))
require.NotEqual(t, "null", string(fields["maxPriorityFeePerGas"]))

// unsigned tx: v/r/s must be "0x0", not null.
require.Equal(t, `"0x0"`, string(fields["v"]))
require.Equal(t, `"0x0"`, string(fields["r"]))
require.Equal(t, `"0x0"`, string(fields["s"]))

// block-placement and sender fields must be absent.
for _, k := range []string{"from", "blockHash", "blockNumber", "blockTimestamp", "transactionIndex"} {
_, present := fields[k]
require.False(t, present, "%s must be absent", k)
}
}

func TestSignTransactionResultMarshalJSON_Legacy(t *testing.T) {
to := common.HexToAddress("0x1234567890123456789012345678901234567890")
tx := &types.LegacyTx{
CommonTx: types.CommonTx{
Nonce: 1,
GasLimit: 21000,
To: &to,
},
GasPrice: *uint256.NewInt(1e9),
}
var buf bytes.Buffer
require.NoError(t, tx.MarshalBinary(&buf))

fields := txFields(t, SignTransactionResult{Raw: buf.Bytes(), Tx: NewRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil)})

// Legacy: gasPrice must be set.
gasPrice, ok := fields["gasPrice"]
require.True(t, ok, "gasPrice must be present")
require.NotEqual(t, "null", string(gasPrice))

// Legacy: maxFeePerGas and maxPriorityFeePerGas are inapplicable and must be null (matching Geth).
require.Equal(t, "null", string(fields["maxFeePerGas"]), "maxFeePerGas must be null for legacy tx")
require.Equal(t, "null", string(fields["maxPriorityFeePerGas"]), "maxPriorityFeePerGas must be null for legacy tx")

// unsigned tx: v/r/s must be "0x0", not null.
require.Equal(t, `"0x0"`, string(fields["v"]))
require.Equal(t, `"0x0"`, string(fields["r"]))
require.Equal(t, `"0x0"`, string(fields["s"]))
}

func TestSignTransactionResultMarshalJSON_NilTx(t *testing.T) {
_, err := json.Marshal(SignTransactionResult{Tx: nil})
require.Error(t, err)
require.Contains(t, err.Error(), "nil transaction")
}

func TestNewRPCTransaction_AccessList_AllZeroSig(t *testing.T) {
to := common.HexToAddress("0x1234567890123456789012345678901234567890")
chainID := uint256.NewInt(1)
Expand Down
1 change: 1 addition & 0 deletions rpc/jsonrpc/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ type EthAPI interface {
SendTransaction(_ context.Context, txObject any) (common.Hash, error)
Sign(ctx context.Context, _ common.Address, _ hexutil.Bytes) (hexutil.Bytes, error)
SignTransaction(_ context.Context, txObject any) (common.Hash, error)
FillTransaction(ctx context.Context, args ethapi.CallArgs) (*ethapi.SignTransactionResult, error)
GetProof(ctx context.Context, address common.Address, storageKeys []hexutil.Bytes, blockNr *rpc.BlockNumberOrHash) (*accounts.AccProofResult, error)
CreateAccessList(ctx context.Context, args ethapi.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash, overrides *ethapi2.StateOverrides, optimizeGas *bool) (*accessListResult, error)

Expand Down
Loading
Loading