Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ var (
utils.AllowUnprotectedTxs,
utils.BatchRequestLimit,
utils.BatchResponseMaxSize,
utils.RPCTxSyncDefaultFlag,
utils.RPCTxSyncMaxFlag,
}

metricsFlags = []cli.Flag{
Expand Down
18 changes: 18 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,18 @@ var (
Value: ethconfig.Defaults.LogQueryLimit,
Category: flags.APICategory,
}
RPCTxSyncDefaultFlag = &cli.DurationFlag{
Name: "rpc.txsync.default",
Usage: "Default timeout for eth_sendRawTransactionSync (e.g. 2s, 500ms)",
Value: ethconfig.Defaults.TxSyncDefaultTimeout,
Category: flags.APICategory,
}
RPCTxSyncMaxFlag = &cli.DurationFlag{
Name: "rpc.txsync.max",
Usage: "Maximum allowed timeout for eth_sendRawTransactionSync (e.g. 5m)",
Value: ethconfig.Defaults.TxSyncMaxTimeout,
Category: flags.APICategory,
}
// Authenticated RPC HTTP settings
AuthListenFlag = &cli.StringFlag{
Name: "authrpc.addr",
Expand Down Expand Up @@ -1717,6 +1729,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
if ctx.IsSet(RPCGlobalLogQueryLimit.Name) {
cfg.LogQueryLimit = ctx.Int(RPCGlobalLogQueryLimit.Name)
}
if ctx.IsSet(RPCTxSyncDefaultFlag.Name) {
cfg.TxSyncDefaultTimeout = ctx.Duration(RPCTxSyncDefaultFlag.Name)
}
if ctx.IsSet(RPCTxSyncMaxFlag.Name) {
cfg.TxSyncMaxTimeout = ctx.Duration(RPCTxSyncMaxFlag.Name)
}
if !ctx.Bool(SnapshotFlag.Name) || cfg.SnapshotCache == 0 {
// If snap-sync is requested, this flag is also required
if cfg.SyncMode == ethconfig.SnapSync {
Expand Down
8 changes: 8 additions & 0 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,11 @@ func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, re
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
}

func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration {
return b.eth.config.TxSyncDefaultTimeout
}

func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration {
return b.eth.config.TxSyncMaxTimeout
}
48 changes: 27 additions & 21 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,27 +49,29 @@ var FullNodeGPO = gasprice.Config{

// Defaults contains default settings for use on the Ethereum main net.
var Defaults = Config{
HistoryMode: history.KeepAll,
SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000,
TransactionHistory: 2350000,
LogHistory: 2350000,
StateHistory: params.FullImmutabilityThreshold,
DatabaseCache: 512,
TrieCleanCache: 154,
TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute,
SnapshotCache: 102,
FilterLogCacheSize: 32,
LogQueryLimit: 1000,
Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig,
BlobPool: blobpool.DefaultConfig,
RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether
HistoryMode: history.KeepAll,
SyncMode: SnapSync,
NetworkId: 0, // enable auto configuration of networkID == chainID
TxLookupLimit: 2350000,
TransactionHistory: 2350000,
LogHistory: 2350000,
StateHistory: params.FullImmutabilityThreshold,
DatabaseCache: 512,
TrieCleanCache: 154,
TrieDirtyCache: 256,
TrieTimeout: 60 * time.Minute,
SnapshotCache: 102,
FilterLogCacheSize: 32,
LogQueryLimit: 1000,
Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig,
BlobPool: blobpool.DefaultConfig,
RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO,
RPCTxFeeCap: 1, // 1 ether
TxSyncDefaultTimeout: 20 * time.Second,
TxSyncMaxTimeout: 1 * time.Minute,
}

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

// OverrideVerkle (TODO: remove after the fork)
OverrideVerkle *uint64 `toml:",omitempty"`

// EIP-7966: eth_sendRawTransactionSync timeouts
TxSyncDefaultTimeout time.Duration `toml:",omitempty"`
TxSyncMaxTimeout time.Duration `toml:",omitempty"`
}

// CreateConsensusEngine creates a consensus engine for the given chain config.
Expand Down
120 changes: 120 additions & 0 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1636,6 +1636,126 @@ func (api *TransactionAPI) SendRawTransaction(ctx context.Context, input hexutil
return SubmitTransaction(ctx, api.b, tx)
}

// SendRawTransactionSync will add the signed transaction to the transaction pool
// and wait until the transaction has been included in a block and return the receipt, or the timeout.
func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hexutil.Bytes, timeoutMs *hexutil.Uint64) (map[string]interface{}, error) {
tx := new(types.Transaction)
if err := tx.UnmarshalBinary(input); err != nil {
return nil, err
}
hash, err := SubmitTransaction(ctx, api.b, tx)
if err != nil {
return nil, err
}

maxTimeout := api.b.RPCTxSyncMaxTimeout()
defaultTimeout := api.b.RPCTxSyncDefaultTimeout()

timeout := defaultTimeout
if timeoutMs != nil && *timeoutMs > 0 {
req := time.Duration(*timeoutMs) * time.Millisecond
if req > maxTimeout {
timeout = maxTimeout
} else {
timeout = req
}
}

receiptCtx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// Fast path.
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
return r, nil
}

// Subscribe to new block events and check the receipt on each new block.
heads := make(chan core.ChainHeadEvent, 16)
sub := api.b.SubscribeChainHeadEvent(heads)
defer sub.Unsubscribe()
subErrCh := sub.Err()

// calculate poll/settle intervals
const (
pollFraction = 20
pollMin = 25 * time.Millisecond
pollMax = 500 * time.Millisecond
)
settleInterval := timeout / pollFraction
if settleInterval < pollMin {
settleInterval = pollMin
} else if settleInterval > pollMax {
settleInterval = pollMax
}

// Settle: short delay to bridge receipt-indexing lag after a new head.
// resetSettle re-arms a single timer safely (stop+drain+reset).
// On head: check once immediately, then reset; on timer: re-check; repeat until deadline.
var (
settle *time.Timer
settleCh <-chan time.Time
)
resetSettle := func(d time.Duration) {
if settle == nil {
settle = time.NewTimer(d)
settleCh = settle.C
return
}
if !settle.Stop() {
select {
case <-settle.C:
default:
}
}
settle.Reset(d)
}
defer func() {
if settle != nil && !settle.Stop() {
select {
case <-settle.C:
default:
}
}
}()

// Start a settle cycle immediately to cover a missed-head race.
resetSettle(settleInterval)

for {
select {
case <-receiptCtx.Done():
if err := ctx.Err(); err != nil {
return nil, err
}
return nil, &txSyncTimeoutError{
msg: fmt.Sprintf("The transaction was added to the transaction pool but wasn't processed in %v.", timeout),
hash: hash,
}

case err, ok := <-subErrCh:
if !ok || err == nil {
// subscription closed; disable this case and rely on settle timer
subErrCh = nil
continue
}
return nil, err

case <-heads:
// Immediate re-check on new head, then grace to bridge indexing lag.
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
return r, nil
}
resetSettle(settleInterval)

case <-settleCh:
if r, err := api.GetTransactionReceipt(receiptCtx, hash); err == nil && r != nil {
return r, nil
}
resetSettle(settleInterval)
}
}
}

// Sign calculates an ECDSA signature for:
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message).
//
Expand Down
Loading
Loading