Skip to content

Commit ff54ca0

Browse files
aodhgangawniegs1na
authored
internal/ethapi: add eth_SendRawTransactionSync (#32830)
New RPC method eth_sendRawTransactionSync(rawTx, timeoutMs?) that submits a signed tx and blocks until a receipt is available or a timeout elapses. Two CLI flags to tune server-side limits: --rpc.txsync.defaulttimeout (default wait window) --rpc.txsync.maxtimeout (upper bound; requests are clamped) closes #32094 --------- Co-authored-by: aodhgan <gawnieg@gmail.com> Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
1 parent c37bd67 commit ff54ca0

File tree

9 files changed

+359
-26
lines changed

9 files changed

+359
-26
lines changed

cmd/geth/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ var (
188188
utils.AllowUnprotectedTxs,
189189
utils.BatchRequestLimit,
190190
utils.BatchResponseMaxSize,
191+
utils.RPCTxSyncDefaultTimeoutFlag,
192+
utils.RPCTxSyncMaxTimeoutFlag,
191193
}
192194

193195
metricsFlags = []cli.Flag{

cmd/utils/flags.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,18 @@ var (
615615
Value: ethconfig.Defaults.LogQueryLimit,
616616
Category: flags.APICategory,
617617
}
618+
RPCTxSyncDefaultTimeoutFlag = &cli.DurationFlag{
619+
Name: "rpc.txsync.defaulttimeout",
620+
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
621+
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
622+
Category: flags.APICategory,
623+
}
624+
RPCTxSyncMaxTimeoutFlag = &cli.DurationFlag{
625+
Name: "rpc.txsync.maxtimeout",
626+
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
627+
Value: ethconfig.Defaults.TxSyncMaxTimeout,
628+
Category: flags.APICategory,
629+
}
618630
// Authenticated RPC HTTP settings
619631
AuthListenFlag = &cli.StringFlag{
620632
Name: "authrpc.addr",
@@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
17171729
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
17181730
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
17191731
}
1732+
if ctx.IsSet(RPCTxSyncDefaultTimeoutFlag.Name) {
1733+
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultTimeoutFlag.Name)
1734+
}
1735+
if ctx.IsSet(RPCTxSyncMaxTimeoutFlag.Name) {
1736+
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxTimeoutFlag.Name)
1737+
}
17201738
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
17211739
// If snap-sync is requested, this flag is also required
17221740
if cfg.SyncMode == ethconfig.SnapSync {

eth/api_backend.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
486486
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
487487
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
488488
}
489+
490+
func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
491+
return b.eth.config.TxSyncDefaultTimeout
492+
}
493+
494+
func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
495+
return b.eth.config.TxSyncMaxTimeout
496+
}

eth/ethconfig/config.go

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{
4949

5050
// Defaults contains default settings for use on the Ethereum main net.
5151
var Defaults = Config{
52-
HistoryMode: history.KeepAll,
53-
SyncMode: SnapSync,
54-
NetworkId: 0, // enable auto configuration of networkID == chainID
55-
TxLookupLimit: 2350000,
56-
TransactionHistory: 2350000,
57-
LogHistory: 2350000,
58-
StateHistory: params.FullImmutabilityThreshold,
59-
DatabaseCache: 512,
60-
TrieCleanCache: 154,
61-
TrieDirtyCache: 256,
62-
TrieTimeout: 60 * time.Minute,
63-
SnapshotCache: 102,
64-
FilterLogCacheSize: 32,
65-
LogQueryLimit: 1000,
66-
Miner: miner.DefaultConfig,
67-
TxPool: legacypool.DefaultConfig,
68-
BlobPool: blobpool.DefaultConfig,
69-
RPCGasCap: 50000000,
70-
RPCEVMTimeout: 5 * time.Second,
71-
GPO: FullNodeGPO,
72-
RPCTxFeeCap: 1, // 1 ether
52+
HistoryMode: history.KeepAll,
53+
SyncMode: SnapSync,
54+
NetworkId: 0, // enable auto configuration of networkID == chainID
55+
TxLookupLimit: 2350000,
56+
TransactionHistory: 2350000,
57+
LogHistory: 2350000,
58+
StateHistory: params.FullImmutabilityThreshold,
59+
DatabaseCache: 512,
60+
TrieCleanCache: 154,
61+
TrieDirtyCache: 256,
62+
TrieTimeout: 60 * time.Minute,
63+
SnapshotCache: 102,
64+
FilterLogCacheSize: 32,
65+
LogQueryLimit: 1000,
66+
Miner: miner.DefaultConfig,
67+
TxPool: legacypool.DefaultConfig,
68+
BlobPool: blobpool.DefaultConfig,
69+
RPCGasCap: 50000000,
70+
RPCEVMTimeout: 5 * time.Second,
71+
GPO: FullNodeGPO,
72+
RPCTxFeeCap: 1, // 1 ether
73+
TxSyncDefaultTimeout: 20 * time.Second,
74+
TxSyncMaxTimeout: 1 * time.Minute,
7375
}
7476

7577
//go:generate go run github.com/fjl/gencodec -type Config -formats toml -out gen_config.go
@@ -183,6 +185,10 @@ type Config struct {
183185

184186
// OverrideVerkle (TODO: remove after the fork)
185187
OverrideVerkle *uint64 `toml:",omitempty"`
188+
189+
// EIP-7966: eth_sendRawTransactionSync timeouts
190+
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
191+
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
186192
}
187193

188194
// CreateConsensusEngine creates a consensus engine for the given chain config.

ethclient/ethclient.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"errors"
2424
"fmt"
2525
"math/big"
26+
"time"
2627

2728
"github.com/ethereum/go-ethereum"
2829
"github.com/ethereum/go-ethereum/common"
@@ -705,6 +706,39 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
705706
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", hexutil.Encode(data))
706707
}
707708

709+
// SendTransactionSync submits a signed tx and waits for a receipt (or until
710+
// the optional timeout elapses on the server side). If timeout == 0, the server
711+
// uses its default.
712+
func (ec *Client) SendTransactionSync(
713+
ctx context.Context,
714+
tx *types.Transaction,
715+
timeout *time.Duration,
716+
) (*types.Receipt, error) {
717+
raw, err := tx.MarshalBinary()
718+
if err != nil {
719+
return nil, err
720+
}
721+
return ec.SendRawTransactionSync(ctx, raw, timeout)
722+
}
723+
724+
func (ec *Client) SendRawTransactionSync(
725+
ctx context.Context,
726+
rawTx []byte,
727+
timeout *time.Duration,
728+
) (*types.Receipt, error) {
729+
var ms *hexutil.Uint64
730+
if timeout != nil {
731+
if d := hexutil.Uint64(timeout.Milliseconds()); d > 0 {
732+
ms = &d
733+
}
734+
}
735+
var receipt types.Receipt
736+
if err := ec.c.CallContext(ctx, &receipt, "eth_sendRawTransactionSync", hexutil.Bytes(rawTx), ms); err != nil {
737+
return nil, err
738+
}
739+
return &receipt, nil
740+
}
741+
708742
// RevertErrorData returns the 'revert reason' data of a contract call.
709743
//
710744
// This can be used with CallContract and EstimateGas, and only when the server is Geth.

internal/ethapi/api.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import (
5555
const estimateGasErrorRatio = 0.015
5656

5757
var errBlobTxNotSupported = errors.New("signing blob transactions not supported")
58+
var errSubClosed = errors.New("chain subscription closed")
5859

5960
// EthereumAPI provides an API to access Ethereum related information.
6061
type EthereumAPI struct {
@@ -1666,6 +1667,92 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
16661667
return SubmitTransaction(ctx, api.b, tx)
16671668
}
16681669

1670+
// SendRawTransactionSync will add the signed transaction to the transaction pool
1671+
// and wait until the transaction has been included in a block and return the receipt, or the timeout.
1672+
func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) {
1673+
tx := new(types.Transaction)
1674+
if err := tx.UnmarshalBinary(input); err != nil {
1675+
return nil, err
1676+
}
1677+
1678+
ch := make(chan core.ChainEvent, 128)
1679+
sub := api.b.SubscribeChainEvent(ch)
1680+
subErrCh := sub.Err()
1681+
defer sub.Unsubscribe()
1682+
1683+
hash, err := SubmitTransaction(ctx, api.b, tx)
1684+
if err != nil {
1685+
return nil, err
1686+
}
1687+
1688+
maxTimeout := api.b.RPCTxSyncMaxTimeout()
1689+
defaultTimeout := api.b.RPCTxSyncDefaultTimeout()
1690+
1691+
timeout := defaultTimeout
1692+
if timeoutMs != nil && *timeoutMs > 0 {
1693+
req := time.Duration(*timeoutMs) * time.Millisecond
1694+
if req > maxTimeout {
1695+
timeout = maxTimeout
1696+
} else {
1697+
timeout = req
1698+
}
1699+
}
1700+
1701+
receiptCtx, cancel := context.WithTimeout(ctx, timeout)
1702+
defer cancel()
1703+
1704+
// Fast path.
1705+
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
1706+
return r, nil
1707+
}
1708+
1709+
for {
1710+
select {
1711+
case <-receiptCtx.Done():
1712+
// If server-side wait window elapsed, return the structured timeout.
1713+
if errors.Is(receiptCtx.Err(), context.DeadlineExceeded) {
1714+
return nil, &txSyncTimeoutError{
1715+
msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v.", timeout),
1716+
hash: hash,
1717+
}
1718+
}
1719+
return nil, receiptCtx.Err()
1720+
1721+
case err, ok := <-subErrCh:
1722+
if !ok {
1723+
return nil, errSubClosed
1724+
}
1725+
return nil, err
1726+
1727+
case ev, ok := <-ch:
1728+
if !ok {
1729+
return nil, errSubClosed
1730+
}
1731+
rs := ev.Receipts
1732+
txs := ev.Transactions
1733+
if len(rs) == 0 || len(rs) != len(txs) {
1734+
continue
1735+
}
1736+
for i := range rs {
1737+
if rs[i].TxHash == hash {
1738+
if rs[i].BlockNumber != nil && rs[i].BlockHash != (common.Hash{}) {
1739+
signer := types.LatestSigner(api.b.ChainConfig())
1740+
return MarshalReceipt(
1741+
rs[i],
1742+
rs[i].BlockHash,
1743+
rs[i].BlockNumber.Uint64(),
1744+
signer,
1745+
txs[i],
1746+
int(rs[i].TransactionIndex),
1747+
), nil
1748+
}
1749+
return api.GetTransactionReceipt(receiptCtx, hash)
1750+
}
1751+
}
1752+
}
1753+
}
1754+
}
1755+
16691756
// Sign calculates an ECDSA signature for:
16701757
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
16711758
//

0 commit comments

Comments
 (0)