Commit ba2bfdc9 authored by Sebastian Stammler's avatar Sebastian Stammler Committed by GitHub

txmgr: Add custom publish error message handling (#13856)

* txmgr: Add custom publish error message handling

Fixes #13739

* add prefix TXMGR to env var
Co-authored-by: default avatarprotolambda <proto@protolambda.com>

---------
Co-authored-by: default avatarprotolambda <proto@protolambda.com>
parent b8c011f1
...@@ -27,17 +27,18 @@ const ( ...@@ -27,17 +27,18 @@ const (
HDPathFlagName = "hd-path" HDPathFlagName = "hd-path"
PrivateKeyFlagName = "private-key" PrivateKeyFlagName = "private-key"
// TxMgr Flags (new + legacy + some shared flags) // TxMgr Flags (new + legacy + some shared flags)
NumConfirmationsFlagName = "num-confirmations" NumConfirmationsFlagName = "num-confirmations"
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count" SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
FeeLimitMultiplierFlagName = "fee-limit-multiplier" FeeLimitMultiplierFlagName = "fee-limit-multiplier"
FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold" FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold"
MinBaseFeeFlagName = "txmgr.min-basefee" MinBaseFeeFlagName = "txmgr.min-basefee"
MinTipCapFlagName = "txmgr.min-tip-cap" MinTipCapFlagName = "txmgr.min-tip-cap"
ResubmissionTimeoutFlagName = "resubmission-timeout" ResubmissionTimeoutFlagName = "resubmission-timeout"
NetworkTimeoutFlagName = "network-timeout" NetworkTimeoutFlagName = "network-timeout"
TxSendTimeoutFlagName = "txmgr.send-timeout" TxSendTimeoutFlagName = "txmgr.send-timeout"
TxNotInMempoolTimeoutFlagName = "txmgr.not-in-mempool-timeout" TxNotInMempoolTimeoutFlagName = "txmgr.not-in-mempool-timeout"
ReceiptQueryIntervalFlagName = "txmgr.receipt-query-interval" ReceiptQueryIntervalFlagName = "txmgr.receipt-query-interval"
AlreadyPublishedCustomErrsFlagName = "txmgr.already-published-custom-errs"
) )
var ( var (
...@@ -191,28 +192,34 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl ...@@ -191,28 +192,34 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
Value: defaults.ReceiptQueryInterval, Value: defaults.ReceiptQueryInterval,
EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"), EnvVars: prefixEnvVars("TXMGR_RECEIPT_QUERY_INTERVAL"),
}, },
&cli.StringSliceFlag{
Name: AlreadyPublishedCustomErrsFlagName,
Usage: "List of custom RPC error messages that indicate that a transaction has already been published.",
EnvVars: prefixEnvVars("TXMGR_ALREADY_PUBLISHED_CUSTOM_ERRS"),
},
}, opsigner.CLIFlags(envPrefix, "")...) }, opsigner.CLIFlags(envPrefix, "")...)
} }
type CLIConfig struct { type CLIConfig struct {
L1RPCURL string L1RPCURL string
Mnemonic string Mnemonic string
HDPath string HDPath string
SequencerHDPath string SequencerHDPath string
L2OutputHDPath string L2OutputHDPath string
PrivateKey string PrivateKey string
SignerCLIConfig opsigner.CLIConfig SignerCLIConfig opsigner.CLIConfig
NumConfirmations uint64 NumConfirmations uint64
SafeAbortNonceTooLowCount uint64 SafeAbortNonceTooLowCount uint64
FeeLimitMultiplier uint64 FeeLimitMultiplier uint64
FeeLimitThresholdGwei float64 FeeLimitThresholdGwei float64
MinBaseFeeGwei float64 MinBaseFeeGwei float64
MinTipCapGwei float64 MinTipCapGwei float64
ResubmissionTimeout time.Duration ResubmissionTimeout time.Duration
ReceiptQueryInterval time.Duration ReceiptQueryInterval time.Duration
NetworkTimeout time.Duration NetworkTimeout time.Duration
TxSendTimeout time.Duration TxSendTimeout time.Duration
TxNotInMempoolTimeout time.Duration TxNotInMempoolTimeout time.Duration
AlreadyPublishedCustomErrs []string
} }
func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig { func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig {
...@@ -270,24 +277,25 @@ func (m CLIConfig) Check() error { ...@@ -270,24 +277,25 @@ func (m CLIConfig) Check() error {
func ReadCLIConfig(ctx *cli.Context) CLIConfig { func ReadCLIConfig(ctx *cli.Context) CLIConfig {
return CLIConfig{ return CLIConfig{
L1RPCURL: ctx.String(L1RPCFlagName), L1RPCURL: ctx.String(L1RPCFlagName),
Mnemonic: ctx.String(MnemonicFlagName), Mnemonic: ctx.String(MnemonicFlagName),
HDPath: ctx.String(HDPathFlagName), HDPath: ctx.String(HDPathFlagName),
SequencerHDPath: ctx.String(SequencerHDPathFlag.Name), SequencerHDPath: ctx.String(SequencerHDPathFlag.Name),
L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name), L2OutputHDPath: ctx.String(L2OutputHDPathFlag.Name),
PrivateKey: ctx.String(PrivateKeyFlagName), PrivateKey: ctx.String(PrivateKeyFlagName),
SignerCLIConfig: opsigner.ReadCLIConfig(ctx), SignerCLIConfig: opsigner.ReadCLIConfig(ctx),
NumConfirmations: ctx.Uint64(NumConfirmationsFlagName), NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName), SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName), FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName),
FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName), FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName),
MinBaseFeeGwei: ctx.Float64(MinBaseFeeFlagName), MinBaseFeeGwei: ctx.Float64(MinBaseFeeFlagName),
MinTipCapGwei: ctx.Float64(MinTipCapFlagName), MinTipCapGwei: ctx.Float64(MinTipCapFlagName),
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName), ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName), ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName), NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
TxSendTimeout: ctx.Duration(TxSendTimeoutFlagName), TxSendTimeout: ctx.Duration(TxSendTimeoutFlagName),
TxNotInMempoolTimeout: ctx.Duration(TxNotInMempoolTimeoutFlagName), TxNotInMempoolTimeout: ctx.Duration(TxNotInMempoolTimeoutFlagName),
AlreadyPublishedCustomErrs: ctx.StringSlice(AlreadyPublishedCustomErrsFlagName),
} }
} }
...@@ -339,16 +347,18 @@ func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) { ...@@ -339,16 +347,18 @@ func NewConfig(cfg CLIConfig, l log.Logger) (*Config, error) {
} }
res := Config{ res := Config{
Backend: l1, Backend: l1,
ChainID: chainID, ChainID: chainID,
TxSendTimeout: cfg.TxSendTimeout, Signer: signerFactory(chainID),
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout, From: from,
NetworkTimeout: cfg.NetworkTimeout,
ReceiptQueryInterval: cfg.ReceiptQueryInterval, TxSendTimeout: cfg.TxSendTimeout,
NumConfirmations: cfg.NumConfirmations, TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount, NetworkTimeout: cfg.NetworkTimeout,
Signer: signerFactory(chainID), ReceiptQueryInterval: cfg.ReceiptQueryInterval,
From: from, NumConfirmations: cfg.NumConfirmations,
SafeAbortNonceTooLowCount: cfg.SafeAbortNonceTooLowCount,
AlreadyPublishedCustomErrs: cfg.AlreadyPublishedCustomErrs,
} }
res.ResubmissionTimeout.Store(int64(cfg.ResubmissionTimeout)) res.ResubmissionTimeout.Store(int64(cfg.ResubmissionTimeout))
...@@ -422,6 +432,10 @@ type Config struct { ...@@ -422,6 +432,10 @@ type Config struct {
// GasPriceEstimatorFn is used to estimate the gas price for a transaction. // GasPriceEstimatorFn is used to estimate the gas price for a transaction.
// If nil, DefaultGasPriceEstimatorFn is used. // If nil, DefaultGasPriceEstimatorFn is used.
GasPriceEstimatorFn GasPriceEstimatorFn GasPriceEstimatorFn GasPriceEstimatorFn
// List of custom RPC error messages that indicate that a transaction has
// already been published.
AlreadyPublishedCustomErrs []string
} }
func (m *Config) Check() error { func (m *Config) Check() error {
......
...@@ -638,9 +638,14 @@ func (m *SimpleTxManager) publishTx(ctx context.Context, tx *types.Transaction, ...@@ -638,9 +638,14 @@ func (m *SimpleTxManager) publishTx(ctx context.Context, tx *types.Transaction,
cancel() cancel()
sendState.ProcessSendError(err) sendState.ProcessSendError(err)
if err == nil { if err == nil || errStringContainsAny(err, m.cfg.AlreadyPublishedCustomErrs) {
// only empty error strings are recorded as successful publishes
m.metr.TxPublished("") m.metr.TxPublished("")
l.Info("Transaction successfully published", "tx", tx.Hash()) if err == nil {
l.Info("Transaction successfully published", "tx", tx.Hash())
} else {
l.Info("Transaction successfully published (custom RPC error)", "tx", tx.Hash(), "err", err)
}
// Tx made it into the mempool, so we'll need a fee bump if we end up trying to replace // Tx made it into the mempool, so we'll need a fee bump if we end up trying to replace
// it with another publish attempt. // it with another publish attempt.
sendState.bumpFees = true sendState.bumpFees = true
...@@ -1053,6 +1058,19 @@ func errStringMatch(err, target error) bool { ...@@ -1053,6 +1058,19 @@ func errStringMatch(err, target error) bool {
return strings.Contains(err.Error(), target.Error()) return strings.Contains(err.Error(), target.Error())
} }
func errStringContainsAny(err error, targets []string) bool {
if err == nil || len(targets) == 0 {
return false
}
for _, target := range targets {
if strings.Contains(err.Error(), target) {
return true
}
}
return false
}
// finishBlobTx finishes creating a blob tx message by safely converting bigints to uint256 // finishBlobTx finishes creating a blob tx message by safely converting bigints to uint256
func finishBlobTx(message *types.BlobTx, chainID, tip, fee, blobFee, value *big.Int) error { func finishBlobTx(message *types.BlobTx, chainID, tip, fee, blobFee, value *big.Int) error {
var o bool var o bool
......
...@@ -1581,6 +1581,29 @@ func TestCloseWaitingForConfirmation(t *testing.T) { ...@@ -1581,6 +1581,29 @@ func TestCloseWaitingForConfirmation(t *testing.T) {
}) })
} }
func TestTxMgrCustomPublishError(t *testing.T) {
customErr := errors.New("custom test error")
testSendVariants(t, func(t *testing.T, send testSendVariantsFn) {
cfg := configWithNumConfs(1)
cfg.AlreadyPublishedCustomErrs = []string{customErr.Error()}
h := newTestHarnessWithConfig(t, cfg)
sendTx := func(ctx context.Context, tx *types.Transaction) error {
txHash := tx.Hash()
h.backend.mine(&txHash, tx.GasFeeCap(), nil)
return customErr
}
h.backend.setTxSender(sendTx)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
receipt, err := send(ctx, h, h.createTxCandidate())
require.Nil(t, err)
require.NotNil(t, receipt)
})
}
func TestMakeSidecar(t *testing.T) { func TestMakeSidecar(t *testing.T) {
var blob eth.Blob var blob eth.Blob
_, err := rand.Read(blob[:]) _, err := rand.Read(blob[:])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment