Skip to content

Make driver access list fetch optional#4353

Open
squadgazzz wants to merge 6 commits intomainfrom
feat/driver-optional-access-list-and-deser-warn
Open

Make driver access list fetch optional#4353
squadgazzz wants to merge 6 commits intomainfrom
feat/driver-optional-access-list-and-deser-warn

Conversation

@squadgazzz
Copy link
Copy Markdown
Contributor

@squadgazzz squadgazzz commented Apr 21, 2026

Description

The driver currently aborts the whole settlement whenever simulator.access_list fails. The access list is only functionally required for trades that send ETH to a smart-contract receiver (the EVM hard gas limit bypass described at settlement.rs line 140). For every other settlement it is just a gas optimization, so a failed RPC should not cost the solver the auction.

Changes

  • Access list is fetched best-effort when the partial access list is empty (no ETH->contract trade). On a non-revert failure (transport, deserialization, etc.) the driver now logs a warn and falls back to the empty partial list instead of aborting the settlement.
  • Revert errors are always propagated, even with an empty partial list, since they signal a real problem with the settlement.

How to test

Existing tests.

@squadgazzz squadgazzz marked this pull request as ready for review April 21, 2026 11:31
@squadgazzz squadgazzz requested a review from a team as a code owner April 21, 2026 11:31
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces enhanced logging for request deserialization and makes access list estimation best-effort for certain settlements. Specifically, it adds custom Axum extractors for JSON and Query types that log warnings on failure. It also updates the settlement simulation logic to treat access list fetch failures as non-fatal warnings when no ETH-to-contract transfers are involved, preventing unnecessary transaction aborts. I have no feedback to provide.

Comment thread crates/driver/src/domain/competition/solution/settlement.rs Outdated
Comment thread crates/driver/src/infra/api/extract.rs Outdated
Comment thread crates/driver/src/infra/api/routes/settle/mod.rs Outdated
Comment thread crates/driver/src/domain/competition/solution/settlement.rs Outdated
Comment thread crates/driver/src/domain/competition/solution/settlement.rs Outdated
@squadgazzz squadgazzz requested a review from jmg-duarte April 22, 2026 07:48
Copy link
Copy Markdown
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

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

Sorry, forgot to submit the review yesterday. :(

// the on-chain tx would succeed without it.
let access_list = match simulator.access_list(&tx).await {
Ok(list) => list,
Err(err) if !access_list_required => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think here we need to do stricter error handling. If the error indicates a revert we should still abort. Only if we can't reason about the outcome of the simulation we should assume things are okay (e.g. deserialization error like in the post-mortem, transport error, something else? 🤷‍♂️).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed

Comment thread crates/driver/src/infra/api/extract.rs Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The error handling should be split into a separate PR IMO.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done #4358

A revert from simulator.access_list signals a real problem with the
settlement even when no trade strictly requires the access-list
workaround, so it must still propagate. The empty-partial-list fallback
now fires only for simulator::Error::Other (transport, deserialization,
etc.) where we cannot reason about the simulation outcome.
Revert the LoggingJson / LoggingQuery extractors and the /solve body
parse warning. They land in a follow-up PR so this branch only covers
the access-list-fetch optionality change.
@squadgazzz squadgazzz changed the title Make driver access list fetch optional and warn on deser errors Make driver access list fetch optional Apr 22, 2026
@squadgazzz squadgazzz requested a review from MartinquaXD April 22, 2026 13:39
Comment thread crates/driver/src/domain/competition/solution/settlement.rs Outdated
// settlement.
let access_list = match simulator.access_list(&tx).await {
Ok(list) => list,
Err(simulator::Error::Other(err)) if partial_access_list.is_empty() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you please double check that matching on Error::Other is sufficient / correct here?
What I'm worried about is that the Other variant still contains a bunch of revert related errors internally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Based on this

move |err| {
let err: SimulatorError = err.into();
let tx = match &err {
SimulatorError::Tenderly(tenderly::Error::Http(_)) => None,
SimulatorError::Tenderly(tenderly::Error::Revert(_)) => Some(tx.clone()),
SimulatorError::Tenderly(tenderly::Error::Other(_)) => None,
SimulatorError::Ethereum(_) => Some(tx.clone()),
SimulatorError::GasExceeded(..) => Some(tx.clone()),
};
match tx {
Some(tx) => Error::Revert(RevertError {
err,
tx: tx.clone(),
block,
}),
None => Error::Other(err),
}
}

, the Error::Other is used only for http and Tenderly's "other" errors. Everything else is mapped to Error::Revert.

Comment on lines +375 to +376
/// smart-contract receiver. Returns an empty access list when the trade does
/// not trigger the gas-limit workaround (non-ETH buy or EOA receiver).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if a new type makes sense here: RequiredAccessList.
This function would then return Result<Option<RequiredAccessList>> to indicate on a type level whether we can continue after non-revert errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Makes sense. Done in 5fa6473

Encodes "trade needs the ETH->contract gas-limit workaround" at the
type level. partial_access_list_for returns Option<RequiredAccessList>
(None = not required), the aggregation reduces only the required
entries, and the simulate() fallback keys off is_none() instead of
is_empty() — unambiguous regardless of whether a required entry set
happens to be empty.
@squadgazzz squadgazzz requested a review from MartinquaXD April 22, 2026 15:16
Copy link
Copy Markdown
Contributor

@jmg-duarte jmg-duarte left a comment

Choose a reason for hiding this comment

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

Codewise LGTM but there's space to make the docs clearer

Comment on lines +379 to +389
/// Access list entries covering a trade that strictly requires the
/// ETH→contract gas-limit workaround. Distinct from a bare `eth::AccessList`
/// so the settlement-level fallback can tell "no trade required one" (`None`)
/// apart from "trade required one but the list turned out empty" (`Some`).
#[derive(Debug, Clone)]
struct RequiredAccessList(eth::AccessList);

/// Return the partial access list for a single trade. `None` means the trade
/// does not trigger the gas-limit workaround (non-ETH buy or EOA receiver),
/// so its absence must not be treated as "required but empty".
async fn partial_access_list_for(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

"gas-limit workaround" is a bit abstract here, could you cover what it exactly is?

My understanding it that without the access list the TX would not pass below the gas limit?

Comment on lines +252 to +253
"access list estimation failed; continuing without it (no ETH->contract \
trades)"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Like the other comment, I also don't understand what "ETH->contract" is supposed to mean, is it a transfer?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants