Bundle cancellations testing

We are now testing a frequently requested feature - bundle cancellations!

Bundles can be cancelled explicitly through a new method - eth_cancelBundle, or they can be replaced via eth_sendBundle.

If you wish to be able to cancel your bundle, provide a uuid with your eth_sendBundle request via the new optional userUuid field. When you pass the same uuid to the eth_cancelBundle call, your bundle will be cancelled. If you send us a new bundle via eht_sendBundle with the same uuid, the new bundle will replace your previous one.

Connect to our builder on Goerli at https://relay-goerli.flashbots.net/ or on Sepolia at https://bundle-relay-sepolia.flashbots.net and try it out.

All feedback welcome. userUuid is not final!

New eth_sendBundle optional field userUuid

The new field should be a uuid-formatted string. While you should keep the userUuid secret, just knowing the uuid (or knowing how to generate the same uuid) is not enough to cancel it.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_sendBundle",
  "params": [
    {
      txs,               // Array[String], A list of signed transactions to execute in an atomic bundle
      blockNumber,       // String, a hex encoded block number for which this bundle is valid on
      userUuid,          // (Optional) uuid-formatted String, used to replace previous bundles
      minTimestamp,      // (Optional) Number, the minimum timestamp for which this bundle is valid, in seconds since the unix epoch
      maxTimestamp,      // (Optional) Number, the maximum timestamp for which this bundle is valid, in seconds since the unix epoch
      revertingTxHashes, // (Optional) Array[String], A list of tx hashes that are allowed to revert
    }
  ]
}

New eth_cancelBundle call

The call accepts a list of userUuids to be cancelled.

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "eth_cancelBundle",
  "params": [
    {
      userUuid // uuid-formatted String, all bundles provided with that uuid will be cancelled
    }
  ]
}

Guarantees

This is a best-effort feature.

However, we will try to make either true:

  • the block sent to relays will not contain cancelled bundles, or
  • the block will contain the cancelled bundle, but no transactions or bundles more recent than the bundle cancellation

The bundle is considered cancelled once either the eth_sendBundle or eth_cancelBundle returns 200.

In case of degraded service performance, we will try to not include any cancellable (providing userUuid) bundles.

Technical notes

3 Likes

Instead of userUuid how about just id?

No need to add extra verbiage to “id” when there’s only one id which clearly identifies the eth_sendBundle call.

I also like uid – may be slightly more useful to give a hint that this id should be unique.

Will test soon and drop more feedback in another post

1 Like

I kinda like field name which represent what it does. id or uuid doesnt represent anything.

fwiw, i like id or uuid because it’s short and descriptive - just a unique id referencing this particular bundle.

agreed, the context about what the id represents is implied

re: this morning @ BB weekly sync

the id doesn’t only correspond to one bundle, as might be implied by id (or uuid etc) belonging to sendBundle.

alternative ideas:

  • bidId
  • batchId
  • setId
  • sequenceId
  • operationId

In case of degraded service performance, we will try to not include any cancellable (providing userUuid) bundles.

I think that this behavior is the kernel of a good idea but imposes on users an unnecessary tradeoff – the utility of cancellation optionality vs. the likelihood of inclusion – when deciding to use the userUuid feature.

I think that it would be ideal to allow bundles sent with userUuid to disable the behavior, such as by adding a field "includeIfDegraded": true.

Cancelling a bundle before eth_sendBundle returns is undefined behavior

I think that it could be interesting to add an integer parameter nonce that must strictly increase (by any amount, not necessarily one) between requests with respect to userUuid.

This would also prevent a replay attack vector that was not possible before userUuid, wherein:

  1. The user sends a bundle with the same UUID to multiple builders (or some other malicious MITM intercepts the signed eth_sendBundle payload)
  2. In a 2nd request, user replaces or cancels the bundle
  3. Malicious builder/ MITM resubmits the original bundle to flashbots

This was not previously possible because the eth_sendBundle API was idempotent. The current proposal is to rank bundles in (userUuid, time received) priority. I propose to instead rank by (userUuid, nonce).

Thanks @ward for the thoughtful feedback!

I think that it would be ideal to allow bundles sent with userUuid to disable the behavior, such as by adding a field "includeIfDegraded": true.

I believe this is not needed, as the degraded performance here means that at least the database is down, and bundles might not even be making it into the blocks - optimizing for this case is futile. This has not happened yet since the merge, so I’d say it’s such a corner case that bundles not being included is the least of the problems.

  • Malicious builder/ MITM resubmits the original bundle to flashbots

This is actually much worse than the possibility of cancellation. If anyone is aware of the contents of the bundle they can do much more damage by unbundling the transactions. This is also why we discount replay attacks on replacement and cancellation - if that attack is possible there’s much bigger problems.

This was not previously possible because the eth_sendBundle API was idempotent. The current proposal is to rank bundles in (userUuid, time received) priority. I propose to instead rank by (userUuid, nonce).

Having said the above, I do agree that ranking by nonce will be superior. I’ll try to engage more of the ecosystem participants on this, since the existing replacement APIs only rely on the timestamps.

2 Likes

I think the downside of requirement to use the nonces is that it does make the client stateful, but one could imagine using the current timestamp to achieve the same with better guarantees.

Also, the current running field name is replacementUuid. It’s important to get the name right as the semantics are confusing.

Agreed with right naming. id or uuid are short but. it doesn’t give any context. replacementUuid makes sense.

1 Like

We deployed the cancellations to our mainnet staging env relay-staging.flashbots.net.
The staging env is limited to sending and cancelling bundles, and it will only simulate bundles targeting currently built and the next blocks.
The bundles make it to our staging builder (0x81babe) and you should see the bundles landing on mainnet as you would be using the production endpoint.

Docs PR can be found here add cancellation docs by zeroXbrock · Pull Request #317 · flashbots/flashbots-docs · GitHub

1 Like

@ward for now we have decided to not implement nonces for now. Adding nonces to the API once it’s proven that people want to use it should be simple.

Was the test implementation eth_cancelBundle removed from Goerli and Sepolia? I had some tests that just ran and are getting unknown method: eth_cancelBundle.

Apologies for the inconvenience. Please test now. I have redeployed cancelBundle in both Goerli and Sepolia.

1 Like

thank you
that sorted it
tests green again :wink:

3 Likes