From 20b40a941c2a682d1ae8d3f0098a481dd077c15e Mon Sep 17 00:00:00 2001 From: owen-eth Date: Wed, 8 Apr 2026 11:13:33 -0400 Subject: [PATCH] fix: sim on correct state, retry if not available --- tools/preconf-rpc/sender/sender.go | 32 ++++++++++++++++++++++++------ tools/preconf-rpc/sim/simulator.go | 5 +++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tools/preconf-rpc/sender/sender.go b/tools/preconf-rpc/sender/sender.go index be10be9c7..1f88009c6 100644 --- a/tools/preconf-rpc/sender/sender.go +++ b/tools/preconf-rpc/sender/sender.go @@ -925,7 +925,14 @@ func (t *TxSender) sendBid( logger.Debug("FastSwap transaction - skipping balance check", "sender", txn.Sender.Hex()) } - state := sim.Latest + // Pin simulation to the blockTracker's known latest block to avoid + // stale state when the sim node lags behind the WS-connected blockTracker. + latestBlockNumber := t.blockTracker.LatestBlockNumber() + state := sim.AtBlock(latestBlockNumber) + if bidBlockNo >= latestBlockNumber+2 { + // Missed slot — intermediate block doesn't exist yet, use pending state + state = sim.Pending + } if latestBaseFee.Sign() > 0 && feePerGas.Cmp(latestBaseFee) < 0 { state = sim.Pending } @@ -937,17 +944,30 @@ func (t *TxSender) sendBid( logs, isSwap, err := t.simulator.Simulate(ctx, txn.Raw, state) if err != nil { txn.simFailed = true - logger.Error("Failed to simulate transaction", "error", err, "blockNumber", bidBlockNo) + + // If the tx has commitments for the previous block and sim fails on the + // next block, retry with delay to allow for inclusion confirmation. if len(txn.commitments) > 0 && txn.commitments[0].BlockNumber+1 == int64(bidBlockNo) { - // Could happen that it takes time to get confirmation of txn inclusion - // so simulation would return error but we should retry after a delay to allow - // the transaction to be included + logger.Error("Failed to simulate transaction", "error", err, "blockNumber", bidBlockNo) return bidResult{}, &errRetry{ err: fmt.Errorf("failed to simulate transaction: %w", err), retryAfter: 2 * time.Second, } } - return bidResult{}, fmt.Errorf("failed to simulate transaction: %w", err) + + // NonRetryableError means the tx itself is invalid (revert, bad request) — fatal. + var nonRetryable *sim.NonRetryableError + if errors.As(err, &nonRetryable) { + logger.Error("Failed to simulate transaction", "error", err, "blockNumber", bidBlockNo) + return bidResult{}, fmt.Errorf("failed to simulate transaction: %w", err) + } + + // Transient error (sim node hasn't ingested block yet, network issue) — retryable. + logger.Warn("Simulation transient error, will retry", "error", err, "blockNumber", bidBlockNo) + return bidResult{}, &errRetry{ + err: fmt.Errorf("failed to simulate transaction: %w", err), + retryAfter: 100 * time.Millisecond, + } } txn.simFailed = false if !isRetry { diff --git a/tools/preconf-rpc/sim/simulator.go b/tools/preconf-rpc/sim/simulator.go index f9b4d9284..e4207cb22 100644 --- a/tools/preconf-rpc/sim/simulator.go +++ b/tools/preconf-rpc/sim/simulator.go @@ -64,6 +64,11 @@ var ( Pending SimState = "pending" ) +// AtBlock returns a SimState that pins simulation to a specific block number. +func AtBlock(blockNumber uint64) SimState { + return SimState(fmt.Sprintf("0x%x", blockNumber)) +} + // Simulator is the external rethsim simulator with fallback support type Simulator struct { apiURLs []string