Skip to content

Oev 771 flashbots batch#346

Draft
dimriou wants to merge 11 commits intodevelopfrom
oev-771_flashbots_batch
Draft

Oev 771 flashbots batch#346
dimriou wants to merge 11 commits intodevelopfrom
oev-771_flashbots_batch

Conversation

@dimriou
Copy link
Contributor

@dimriou dimriou commented Feb 4, 2026

This PR enables Flashbots bundles. Every time a transaction is broadcasted, it will fetch all the past in-flight transactions and create a bundle. The bundle will use the first attempt of each transaction. The bundle broadcasting is a fire and forget process that helps nonce unblocking, so it is not tracked.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 4, 2026

⚠️ API Diff Results - Breaking changes detected

📦 Module: github-com-smartcontractkit-chainlink-evm

🔴 Breaking Changes (5)

pkg/config.TransactionManagerV2 (1)
  • Bundles — ➕ Added
pkg/txm.TxStore (2)
  • FetchUnconfirmedTransactions — ➕ Added

  • UpdateSignedAttempt — ➕ Added

pkg/txm/clientwrappers/dualbroadcast (2)
  • NewFlashbotsClient — Type changed:
func(
  - github.com/smartcontractkit/chainlink-evm/pkg/client.Client, 
  + github.com/smartcontractkit/chainlink-common/pkg/logger.Logger, 
  + FlashbotsClientRPC, 
  github.com/smartcontractkit/chainlink-evm/pkg/keys.MessageSigner, 
  - *net/url.URL
  + *net/url.URL, 
  + FlashbotsTxStore, 
  + *bool
)
*FlashbotsClient
  • SelectClient — Type changed:
func(
  github.com/smartcontractkit/chainlink-common/pkg/logger.Logger, 
  github.com/smartcontractkit/chainlink-evm/pkg/client.Client, 
  github.com/smartcontractkit/chainlink-evm/pkg/keys.ChainStore, 
  *net/url.URL, 
  *math/big.Int, 
  - MetaClientTxStore
  + github.com/smartcontractkit/chainlink-evm/pkg/txm.TxStore, 
  + *bool
)
(github.com/smartcontractkit/chainlink-evm/pkg/txm.Client, github.com/smartcontractkit/chainlink-evm/pkg/txm.ErrorHandler, error)

📄 View full apidiff report

@dimriou dimriou force-pushed the oev-771_flashbots_batch branch from 7f1a37b to 6fe2367 Compare February 17, 2026 11:32
@dimriou dimriou force-pushed the oev-771_flashbots_batch branch from 60f915c to a1d5fa8 Compare February 18, 2026 10:12
// Don't act on a bundle error - this is a fire and forget operation but we do want to log the error.
if d.bundles {
if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
d.lggr.Error("error sending bundle: ", err)

Check failure

Code scanning / CodeQL

Log entries created from user input High

This log entry depends on a
user-provided value
.

Copilot Autofix

AI about 3 hours ago

In general, to fix this problem we need to ensure that any user-controlled data that can reach log messages is sanitized before being logged. For plain-text logs, the main risk is log forging via control characters such as \n and \r. The minimal, behavior-preserving fix is to wrap the error returned from signAndPostMessage (and propagated through SendBundle) so that any newline and carriage-return characters in its message are removed before it is logged.

The single best fix here is to sanitize the error string at the log site in Send, where CodeQL identified the vulnerable sink:

if d.bundles {
    if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
        d.lggr.Error("error sending bundle: ", err)
    }
}

We can change this to convert err to a string, strip \n and \r, and then log that sanitized string. This keeps the log semantics (error message text) nearly identical while preventing embedded newlines. We will:

  1. Add a small helper function in the same file to sanitize strings for logging by removing \n and \r (and we can rely on the existing strings import).
  2. Use this helper on err.Error() at the log call site, replacing err with the sanitized message string.

All changes will be confined to pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go. No new imports are required.

Suggested changeset 1
pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go b/pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go
--- a/pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go
+++ b/pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go
@@ -27,6 +27,15 @@
 
 const flashbotsRPCTimeout = 10 * time.Second
 
+// sanitizeLogMessage removes newline and carriage return characters from a log message
+// to reduce the risk of log forging when logging data that may include user input.
+func sanitizeLogMessage(msg string) string {
+	// Remove '\n' and '\r' characters which could be used to forge log entries.
+	msg = strings.ReplaceAll(msg, "\n", "")
+	msg = strings.ReplaceAll(msg, "\r", "")
+	return msg
+}
+
 type FlashbotsTxStore interface {
 	FetchUnconfirmedTransactions(context.Context, common.Address) ([]*types.Transaction, error)
 }
@@ -107,7 +116,7 @@
 		// Don't act on a bundle error - this is a fire and forget operation but we do want to log the error.
 		if d.bundles {
 			if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
-				d.lggr.Error("error sending bundle: ", err)
+				d.lggr.Error("error sending bundle: ", sanitizeLogMessage(err.Error()))
 			}
 		}
 		return nil
EOF
@@ -27,6 +27,15 @@

const flashbotsRPCTimeout = 10 * time.Second

// sanitizeLogMessage removes newline and carriage return characters from a log message
// to reduce the risk of log forging when logging data that may include user input.
func sanitizeLogMessage(msg string) string {
// Remove '\n' and '\r' characters which could be used to forge log entries.
msg = strings.ReplaceAll(msg, "\n", "")
msg = strings.ReplaceAll(msg, "\r", "")
return msg
}

type FlashbotsTxStore interface {
FetchUnconfirmedTransactions(context.Context, common.Address) ([]*types.Transaction, error)
}
@@ -107,7 +116,7 @@
// Don't act on a bundle error - this is a fire and forget operation but we do want to log the error.
if d.bundles {
if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
d.lggr.Error("error sending bundle: ", err)
d.lggr.Error("error sending bundle: ", sanitizeLogMessage(err.Error()))
}
}
return nil
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link

@pszal pszal left a comment

Choose a reason for hiding this comment

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

Nice! Some feedback below. Please consider adding more tests.

if err != nil {
return fmt.Errorf("failed to get current block height: %w", err)
}
targetBlock := currentBlock.NumberU64() + 24
Copy link

Choose a reason for hiding this comment

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

would maxBlock be a more accurate var name?


bodyItems = append(bodyItems, map[string]any{
"tx": hexutil.Encode(txData),
"revertMode": "allow", // we always want to allow reverts so bundles are valid even if a single transaction within the bundle goes through
Copy link

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed, but I followed the spec they gave me in our conversation.

Copy link

Choose a reason for hiding this comment

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

Does it make sense to document this spec somewhere (e.g., doc.go)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a link to the docs.

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.

2 participants