Skip to content

Conversation

@dimriou
Copy link
Contributor

@dimriou dimriou commented Nov 7, 2025

Each commit corresponds to the following features:

  • OEV-676 Sync broadcasting and backfilling
  • OEV-680 Trigger instant retransmission of purgeable transactions
  • OEV-675 Reduce empty transaction gas limit
  • OEV-677 Add Meta Error Handler
  • OEV-668 Add agnostic gas bumping
  • OEV-679 Add separate pricing for empty transactions

@dimriou dimriou force-pushed the oev-671_txm_improvements branch from 8276ab6 to 64f4d2f Compare November 10, 2025 10:51
go t.broadcastLoop(address, triggerCh)
go t.backfillLoop(address)
t.wg.Add(1)
go t.loop(address, triggerCh)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remind me what the advantage was of combining these two loops?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It enables easier nonce management via error handling when broadcasting a transaction.

return err
}

if tx == nil || *tx.Nonce != latestNonce {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when does this scenario happen? hard for me to think of why the local nonce would be behind the chain nonce if it has to originate from the local txm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not behind but in front. Here's an example: you broadcast tx A to the mempool. You restart the TXM, fetch the pending nonce, which is A+1, and start sending more transactions to the mempool. Since you restarted the TXM, transaction A is not being tracked. If it gets dropped by the RPC or the builders, TXM won't be able to recover unless it fills the nonce. That's what this case does.

Copy link

@lhmerino lhmerino Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed TXM uses pending for the block parameter to capture pending transactions.
Does the Flashbots client behave accordingly (i.e., takes into account inflight transactions)?

// TODO: add better handling
bumpedAttempt, err := a.NewBumpAttempt(ctx, lggr, tx, *attempt)
if err != nil {
return attempt, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we select for that specific error so we reduce possibility of halting on a different error?

Copy link
Contributor

@augustbleeds augustbleeds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good to me, just want to clarify the error handling behavior for empty tx gas bumping.

@dimriou dimriou force-pushed the oev-671_txm_improvements branch from 29a1813 to 115e871 Compare November 11, 2025 15:51
}

func (e *errorHandler) HandleError(ctx context.Context, tx *types.Transaction, txErr error, txStore txm.TxStore, setNonce func(common.Address, uint64), isFromBroadcastMethod bool) error {
// If this isn't the first broadcast, don't mark the tx as fatal as other txs might be included on-chain.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// If this isn't the first broadcast, don't mark the tx as fatal as other txs might be included on-chain.
// Only if this is the first broadcast, mark the tx as fatal. In other cases, other txs might be included on-chain


// bump purge attempts
if tx.IsPurgeable {
// TODO: add better handling
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to make a separate ticket with an explanation of what would be better here

for {
bumpedAttempt, err := a.NewBumpAttempt(ctx, lggr, tx, *attempt)
if err != nil {
if errors.Is(err, fees.ErrConnectivity) {
Copy link

@lhmerino lhmerino Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at the error choice. I believe this is due to the code located here

if bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 {
    return ..., ErrConnectivity
}

It feels slightly counterintuitive to return ErrConnectivity for a value check, where ErrBumpFeeExceedsLimit might seem more natural.

Is the intention to treat priorityFeeThreshold as a sanity guardrail (implying the network/estimator is broken if fees get this high) rather than a budget limit (implying the tx just needs to pay more)? If so, adding a small comment explaining why this triggers a 'connectivity' error would be helpful for readability.

defer cancel()
broadcastWithBackoff := newBackoff(1 * time.Second)
var broadcastCh <-chan time.Time
backfillCh := time.After(utils.WithJitter(t.config.BlockTime))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utils.WithJitter is a deprecated function.
It says to use timeutils package instead.

metrics, err := NewMetaMetrics(chainID.String())
if err != nil {
return nil, fmt.Errorf("failed to create Meta metrics: %w", err)
}
Copy link

@lhmerino lhmerino Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the MetaClient currently active -- it looks A-specific?
I noticed a potential dependency in the metrics setup. If beholder is unavailable, will this cause the client to error out as well?

Ideally, we want to ensure the client is decoupled so that a beholder outage doesn't block the main flow. For example, on 10/10 I saw many alerts about beholder potentially having issues.

return NewFlashbotsClient(client, keyStore, url), nil
return NewFlashbotsClient(client, keyStore, url), nil, nil
default:
return NewMetaClient(lggr, client, keyStore, url, chainID)
Copy link

@lhmerino lhmerino Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code could then be made cleaner if NewMetaClient does not fail.


// TODO: for now do the simple thing and drop the transaction instead of adding it to the fatal queue.
delete(m.UnconfirmedTransactions, *txToMark.Nonce)
delete(m.Transactions, txToMark.ID)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few safety concerns about deleting these entries:

  • Nonce Gaps: Could deleting this nonce create a gap in our internal inflight list? I'm concerned that higher-nonce transactions currently inflight might stall indefinitely waiting for the tx associated with this nonce to fill.
  • Panic Risk: Dereferencing *txToMark.Nonce without checking if txToMark.Nonce is not nil can cause a panic.
  • Less important but still relevant: could this complicate debugging by deleting this information?

FromAddress: m.address,
ToAddress: common.Address{},
Value: big.NewInt(0),
SpecifiedGasLimit: gasLimit,
Copy link

@lhmerino lhmerino Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: We could simplify this by hardcoding the gas limit to 21_000.

Since a standard ETH transfer to an EOA (0x0) has a fixed cost (21_000), passing in gasLimit and maintaining a separate EmptyTxLimitDefault config seems like unnecessary overhead. In addition, it would also remove the need to add gasEstimatorConfig parameter to func NewTxmV2.

If we were to instead send ETH to/from a smart contract, only then would the gas amount differ.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants