Skip to content

Commit 1bf5348

Browse files
sebastianstprotolambda
authored andcommitted
txmgr: Add custom publish error message handling (ethereum-optimism#13856)
* txmgr: Add custom publish error message handling Fixes ethereum-optimism#13739 * add prefix TXMGR to env var Co-authored-by: protolambda <[email protected]> --------- Co-authored-by: protolambda <[email protected]>
1 parent 9e59609 commit 1bf5348

File tree

3 files changed

+114
-59
lines changed

3 files changed

+114
-59
lines changed

op-service/txmgr/cli.go

Lines changed: 71 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@ const (
2727
HDPathFlagName = "hd-path"
2828
PrivateKeyFlagName = "private-key"
2929
// TxMgr Flags (new + legacy + some shared flags)
30-
NumConfirmationsFlagName = "num-confirmations"
31-
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
32-
FeeLimitMultiplierFlagName = "fee-limit-multiplier"
33-
FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold"
34-
MinBaseFeeFlagName = "txmgr.min-basefee"
35-
MinTipCapFlagName = "txmgr.min-tip-cap"
36-
ResubmissionTimeoutFlagName = "resubmission-timeout"
37-
NetworkTimeoutFlagName = "network-timeout"
38-
TxSendTimeoutFlagName = "txmgr.send-timeout"
39-
TxNotInMempoolTimeoutFlagName = "txmgr.not-in-mempool-timeout"
40-
ReceiptQueryIntervalFlagName = "txmgr.receipt-query-interval"
30+
NumConfirmationsFlagName = "num-confirmations"
31+
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
32+
FeeLimitMultiplierFlagName = "fee-limit-multiplier"
33+
FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold"
34+
MinBaseFeeFlagName = "txmgr.min-basefee"
35+
MinTipCapFlagName = "txmgr.min-tip-cap"
36+
ResubmissionTimeoutFlagName = "resubmission-timeout"
37+
NetworkTimeoutFlagName = "network-timeout"
38+
TxSendTimeoutFlagName = "txmgr.send-timeout"
39+
TxNotInMempoolTimeoutFlagName = "txmgr.not-in-mempool-timeout"
40+
ReceiptQueryIntervalFlagName = "txmgr.receipt-query-interval"
41+
AlreadyPublishedCustomErrsFlagName = "txmgr.already-published-custom-errs"
4142
)
4243

4344
var (
@@ -191,28 +192,34 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
191192
Value: defaults.ReceiptQueryInterval,
192193
EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"),
193194
},
195+
&cli.StringSliceFlag{
196+
Name: AlreadyPublishedCustomErrsFlagName,
197+
Usage: "List of custom RPC error messages that indicate that a transaction has already been published.",
198+
EnvVars: prefixEnvVars("TXMGR_ALREADY_PUBLISHED_CUSTOM_ERRS"),
199+
},
194200
}, opsigner.CLIFlags(envPrefix, "")...)
195201
}
196202

197203
type CLIConfig struct {
198-
L1RPCURL string
199-
Mnemonic string
200-
HDPath string
201-
SequencerHDPath string
202-
L2OutputHDPath string
203-
PrivateKey string
204-
SignerCLIConfig opsigner.CLIConfig
205-
NumConfirmations uint64
206-
SafeAbortNonceTooLowCount uint64
207-
FeeLimitMultiplier uint64
208-
FeeLimitThresholdGwei float64
209-
MinBaseFeeGwei float64
210-
MinTipCapGwei float64
211-
ResubmissionTimeout time.Duration
212-
ReceiptQueryInterval time.Duration
213-
NetworkTimeout time.Duration
214-
TxSendTimeout time.Duration
215-
TxNotInMempoolTimeout time.Duration
204+
L1RPCURL string
205+
Mnemonic string
206+
HDPath string
207+
SequencerHDPath string
208+
L2OutputHDPath string
209+
PrivateKey string
210+
SignerCLIConfig opsigner.CLIConfig
211+
NumConfirmations uint64
212+
SafeAbortNonceTooLowCount uint64
213+
FeeLimitMultiplier uint64
214+
FeeLimitThresholdGwei float64
215+
MinBaseFeeGwei float64
216+
MinTipCapGwei float64
217+
ResubmissionTimeout time.Duration
218+
ReceiptQueryInterval time.Duration
219+
NetworkTimeout time.Duration
220+
TxSendTimeout time.Duration
221+
TxNotInMempoolTimeout time.Duration
222+
AlreadyPublishedCustomErrs []string
216223
}
217224

218225
func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig {
@@ -270,24 +277,25 @@ func (m CLIConfig) Check() error {
270277

271278
func ReadCLIConfig(ctx *cli.Context) CLIConfig {
272279
return CLIConfig{
273-
L1RPCURL: ctx.String(L1RPCFlagName),
274-
Mnemonic: ctx.String(MnemonicFlagName),
275-
HDPath: ctx.String(HDPathFlagName),
276-
SequencerHDPath: ctx.String(SequencerHDPathFlag.Name),
277-
L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name),
278-
PrivateKey: ctx.String(PrivateKeyFlagName),
279-
SignerCLIConfig: opsigner.ReadCLIConfig(ctx),
280-
NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
281-
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
282-
FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName),
283-
FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName),
284-
MinBaseFeeGwei: ctx.Float64(MinBaseFeeFlagName),
285-
MinTipCapGwei: ctx.Float64(MinTipCapFlagName),
286-
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
287-
ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
288-
NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
289-
TxSendTimeout: ctx.Duration(TxSendTimeoutFlagName),
290-
TxNotInMempoolTimeout: ctx.Duration(TxNotInMempoolTimeoutFlagName),
280+
L1RPCURL: ctx.String(L1RPCFlagName),
281+
Mnemonic: ctx.String(MnemonicFlagName),
282+
HDPath: ctx.String(HDPathFlagName),
283+
SequencerHDPath: ctx.String(SequencerHDPathFlag.Name),
284+
L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name),
285+
PrivateKey: ctx.String(PrivateKeyFlagName),
286+
SignerCLIConfig: opsigner.ReadCLIConfig(ctx),
287+
NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
288+
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
289+
FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName),
290+
FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName),
291+
MinBaseFeeGwei: ctx.Float64(MinBaseFeeFlagName),
292+
MinTipCapGwei: ctx.Float64(MinTipCapFlagName),
293+
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
294+
ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
295+
NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
296+
TxSendTimeout: ctx.Duration(TxSendTimeoutFlagName),
297+
TxNotInMempoolTimeout: ctx.Duration(TxNotInMempoolTimeoutFlagName),
298+
AlreadyPublishedCustomErrs: ctx.StringSlice(AlreadyPublishedCustomErrsFlagName),
291299
}
292300
}
293301

@@ -339,16 +347,18 @@ func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) {
339347
}
340348

341349
res := Config{
342-
Backend: l1,
343-
ChainID: chainID,
344-
TxSendTimeout: cfg.TxSendTimeout,
345-
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
346-
NetworkTimeout: cfg.NetworkTimeout,
347-
ReceiptQueryInterval: cfg.ReceiptQueryInterval,
348-
NumConfirmations: cfg.NumConfirmations,
349-
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
350-
Signer: signerFactory(chainID),
351-
From: from,
350+
Backend: l1,
351+
ChainID: chainID,
352+
Signer: signerFactory(chainID),
353+
From: from,
354+
355+
TxSendTimeout: cfg.TxSendTimeout,
356+
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
357+
NetworkTimeout: cfg.NetworkTimeout,
358+
ReceiptQueryInterval: cfg.ReceiptQueryInterval,
359+
NumConfirmations: cfg.NumConfirmations,
360+
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
361+
AlreadyPublishedCustomErrs: cfg.AlreadyPublishedCustomErrs,
352362
}
353363

354364
res.ResubmissionTimeout.Store(int64(cfg.ResubmissionTimeout))
@@ -422,6 +432,10 @@ type Config struct {
422432
// GasPriceEstimatorFn is used to estimate the gas price for a transaction.
423433
// If nil, DefaultGasPriceEstimatorFn is used.
424434
GasPriceEstimatorFn GasPriceEstimatorFn
435+
436+
// List of custom RPC error messages that indicate that a transaction has
437+
// already been published.
438+
AlreadyPublishedCustomErrs []string
425439
}
426440

427441
func (m *Config) Check() error {

op-service/txmgr/txmgr.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -638,9 +638,14 @@ func (m *SimpleTxManager) publishTx(ctx context.Context, tx *types.Transaction,
638638
cancel()
639639
sendState.ProcessSendError(err)
640640

641-
if err == nil {
641+
if err == nil || errStringContainsAny(err, m.cfg.AlreadyPublishedCustomErrs) {
642+
// only empty error strings are recorded as successful publishes
642643
m.metr.TxPublished("")
643-
l.Info("Transaction successfully published", "tx", tx.Hash())
644+
if err == nil {
645+
l.Info("Transaction successfully published", "tx", tx.Hash())
646+
} else {
647+
l.Info("Transaction successfully published (custom RPC error)", "tx", tx.Hash(), "err", err)
648+
}
644649
// Tx made it into the mempool, so we'll need a fee bump if we end up trying to replace
645650
// it with another publish attempt.
646651
sendState.bumpFees = true
@@ -1053,6 +1058,19 @@ func errStringMatch(err, target error) bool {
10531058
return strings.Contains(err.Error(), target.Error())
10541059
}
10551060

1061+
func errStringContainsAny(err error, targets []string) bool {
1062+
if err == nil || len(targets) == 0 {
1063+
return false
1064+
}
1065+
1066+
for _, target := range targets {
1067+
if strings.Contains(err.Error(), target) {
1068+
return true
1069+
}
1070+
}
1071+
return false
1072+
}
1073+
10561074
// finishBlobTx finishes creating a blob tx message by safely converting bigints to uint256
10571075
func finishBlobTx(message *types.BlobTx, chainID, tip, fee, blobFee, value *big.Int) error {
10581076
var o bool

op-service/txmgr/txmgr_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,29 @@ func TestCloseWaitingForConfirmation(t *testing.T) {
15811581
})
15821582
}
15831583

1584+
func TestTxMgrCustomPublishError(t *testing.T) {
1585+
customErr := errors.New("custom test error")
1586+
1587+
testSendVariants(t, func(t *testing.T, send testSendVariantsFn) {
1588+
cfg := configWithNumConfs(1)
1589+
cfg.AlreadyPublishedCustomErrs = []string{customErr.Error()}
1590+
h := newTestHarnessWithConfig(t, cfg)
1591+
1592+
sendTx := func(ctx context.Context, tx *types.Transaction) error {
1593+
txHash := tx.Hash()
1594+
h.backend.mine(&txHash, tx.GasFeeCap(), nil)
1595+
return customErr
1596+
}
1597+
h.backend.setTxSender(sendTx)
1598+
1599+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1600+
defer cancel()
1601+
receipt, err := send(ctx, h, h.createTxCandidate())
1602+
require.Nil(t, err)
1603+
require.NotNil(t, receipt)
1604+
})
1605+
}
1606+
15841607
func TestMakeSidecar(t *testing.T) {
15851608
var blob eth.Blob
15861609
_, err := rand.Read(blob[:])

0 commit comments

Comments
 (0)