diff --git a/eth/api_backend.go b/eth/api_backend.go index dc84b4d740..9cbe57202a 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -116,9 +116,10 @@ type nonceTracker struct { } type nonceReservation struct { - deadline time.Time - used bool - expired bool + deadline time.Time + used bool + expired bool + firstHash common.Hash } const reservationTTL = 30 * time.Second @@ -253,14 +254,24 @@ func (b *EthAPIBackend) validateAndConsumeSequencerReservation(tx *types.Transac if r == nil { return nil } - if r.used { - return fmt.Errorf("reserved nonce already used: %d", tx.Nonce()) - } // If expired or past deadline, reject to force client to rebuild with a new reservation if r.expired || time.Now().After(r.deadline) { return fmt.Errorf("reserved nonce expired: %d", tx.Nonce()) } + // If the reservation has already been used, allow rebroadcasts or fee-bump + // replacements to proceed. The txpool is the source of truth for + // "already known" and replacement underpricing semantics. Emit a clear log. + if r.used { + if r.firstHash == tx.Hash() { + log.Info("[SSV] Re-received tx for reserved nonce (rebroadcast)", "nonce", tx.Nonce(), "txHash", tx.Hash()) + } else { + log.Info("[SSV] Re-received tx for reserved nonce (replacement)", "nonce", tx.Nonce(), "txHash", tx.Hash(), "firstHash", r.firstHash) + } + return nil + } r.used = true + r.firstHash = tx.Hash() + log.Info("[SSV] Consuming reserved nonce for tx", "nonce", tx.Nonce(), "txHash", tx.Hash()) return nil } diff --git a/eth/api_userops.go b/eth/api_userops.go index 6ffcca52f4..3f98c7cbdf 100644 --- a/eth/api_userops.go +++ b/eth/api_userops.go @@ -580,6 +580,9 @@ func (api *composeUserOpsAPI) BuildSignedUserOpsTx( return nil, fmt.Errorf("sign tx: %w", err) } + // Log the reserved nonce and the resulting tx hash together for traceability. + log.Info("[SSV] Reserved sequencer nonce for tx", "nonce", nonce, "txHash", signedTx.Hash()) + log.Info("[SSV] Signed user transaction with sequencer key", "txHash", signedTx.Hash().Hex(), "chainID", api.b.ChainConfig().ChainID,