@@ -41,7 +41,6 @@ import (
4141 "github.com/ethereum/go-ethereum/crypto"
4242 "github.com/ethereum/go-ethereum/eth/gasestimator"
4343 "github.com/ethereum/go-ethereum/eth/tracers/logger"
44- "github.com/ethereum/go-ethereum/event"
4544 "github.com/ethereum/go-ethereum/internal/ethapi/override"
4645 "github.com/ethereum/go-ethereum/log"
4746 "github.com/ethereum/go-ethereum/p2p"
@@ -1649,61 +1648,111 @@ func (api *TransactionAPI) SendRawTransactionSync(ctx context.Context, input hex
16491648 return nil , err
16501649 }
16511650
1652- // effective timeout: min(requested, max), default if none
1653- max := api .b .RPCTxSyncMaxTimeout ()
1654- def := api .b .RPCTxSyncDefaultTimeout ()
1651+ maxTimeout := api .b .RPCTxSyncMaxTimeout ()
1652+ defaultTimeout := api .b .RPCTxSyncDefaultTimeout ()
16551653
1656- eff := def
1654+ timeout := defaultTimeout
16571655 if timeoutMs != nil && * timeoutMs > 0 {
16581656 req := time .Duration (* timeoutMs ) * time .Millisecond
1659- if req > max {
1660- eff = max
1657+ if req > maxTimeout {
1658+ timeout = maxTimeout
16611659 } else {
1662- eff = req
1660+ timeout = req
16631661 }
16641662 }
16651663
1666- receiptCtx , cancel := context .WithTimeout (ctx , eff )
1664+ receiptCtx , cancel := context .WithTimeout (ctx , timeout )
16671665 defer cancel ()
16681666
1669- // Fast path: maybe already mined/indexed
1667+ // Fast path.
16701668 if r , err := api .GetTransactionReceipt (receiptCtx , hash ); err == nil && r != nil {
16711669 return r , nil
16721670 }
16731671
1674- // Subscribe to head changes and re- check on each new block
1672+ // Subscribe to new block events and check the receipt on each new block.
16751673 heads := make (chan core.ChainHeadEvent , 16 )
1676- var sub event.Subscription = api .b .SubscribeChainHeadEvent (heads )
1677- if sub == nil {
1678- return nil , errors .New ("chain head subscription unavailable" )
1679- }
1674+ sub := api .b .SubscribeChainHeadEvent (heads )
16801675 defer sub .Unsubscribe ()
1676+ subErrCh := sub .Err ()
1677+
1678+ // calculate poll/settle intervals
1679+ const (
1680+ pollFraction = 20
1681+ pollMin = 25 * time .Millisecond
1682+ pollMax = 500 * time .Millisecond
1683+ )
1684+ settleInterval := timeout / pollFraction
1685+ if settleInterval < pollMin {
1686+ settleInterval = pollMin
1687+ } else if settleInterval > pollMax {
1688+ settleInterval = pollMax
1689+ }
1690+
1691+ // Settle: short delay to bridge receipt-indexing lag after a new head.
1692+ // resetSettle re-arms a single timer safely (stop+drain+reset).
1693+ // On head: check once immediately, then reset; on timer: re-check; repeat until deadline.
1694+ var (
1695+ settle * time.Timer
1696+ settleCh <- chan time.Time
1697+ )
1698+ resetSettle := func (d time.Duration ) {
1699+ if settle == nil {
1700+ settle = time .NewTimer (d )
1701+ settleCh = settle .C
1702+ return
1703+ }
1704+ if ! settle .Stop () {
1705+ select {
1706+ case <- settle .C :
1707+ default :
1708+ }
1709+ }
1710+ settle .Reset (d )
1711+ }
1712+ defer func () {
1713+ if settle != nil && ! settle .Stop () {
1714+ select {
1715+ case <- settle .C :
1716+ default :
1717+ }
1718+ }
1719+ }()
1720+
1721+ // Start a settle cycle immediately to cover a missed-head race.
1722+ resetSettle (settleInterval )
16811723
16821724 for {
16831725 select {
16841726 case <- receiptCtx .Done ():
1685- // Distinguish upstream cancellation from our timeout
16861727 if err := ctx .Err (); err != nil {
16871728 return nil , err
16881729 }
16891730 return nil , & txSyncTimeoutError {
1690- msg : fmt .Sprintf ("The transaction was added to the transaction pool but wasn't processed in %v." , eff ),
1731+ msg : fmt .Sprintf ("The transaction was added to the transaction pool but wasn't processed in %v." , timeout ),
16911732 hash : hash ,
16921733 }
16931734
1694- case err := <- sub .Err ():
1735+ case err , ok := <- subErrCh :
1736+ if ! ok || err == nil {
1737+ // subscription closed; disable this case and rely on settle timer
1738+ subErrCh = nil
1739+ continue
1740+ }
16951741 return nil , err
16961742
16971743 case <- heads :
1698- receipt , getErr := api .GetTransactionReceipt (receiptCtx , hash )
1699- // If tx-index still building, keep waiting; ignore transient errors
1700- if getErr != nil && ! errors .Is (getErr , NewTxIndexingError ()) {
1701- // ignore and wait for next head
1702- continue
1744+ // Immediate re-check on new head, then grace to bridge indexing lag.
1745+ if r , err := api .GetTransactionReceipt (receiptCtx , hash ); err == nil && r != nil {
1746+ return r , nil
17031747 }
1704- if receipt != nil {
1705- return receipt , nil
1748+ resetSettle (settleInterval )
1749+
1750+ case <- settleCh :
1751+ r , getErr := api .GetTransactionReceipt (receiptCtx , hash )
1752+ if r != nil && getErr == nil {
1753+ return r , nil
17061754 }
1755+ resetSettle (settleInterval )
17071756 }
17081757 }
17091758}
0 commit comments