Skip to content

Comments

Oev 771 flashbots batch#346

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

Oev 771 flashbots batch#346
dimriou wants to merge 10 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 5 days ago

In general, the fix is to ensure any user-controlled data that may reach logs is encoded or sanitized before being embedded in messages. For plain text logs, at minimum, newline (\n) and carriage return (\r) characters must be removed (or escaped) so an attacker cannot break the log line structure. In this file, the tainted data originates from postReq.URL.String() and the JSON body used to build reqDesc, which is later included in an error and then logged. The safest approach is to sanitize these components when constructing reqDesc, so any derived errors are safe to log anywhere.

The best fix with minimal functional impact is: in signAndPostMessage, before building reqDesc, derive sanitized strings from postReq.URL.String() and body by removing \n and \r (using strings.ReplaceAll, which is already imported). Then construct reqDesc using these sanitized values. This ensures that any errors containing reqDesc cannot inject raw newlines into logs, while preserving the informational content of URL and body. No change is needed at the logging call on line 110; sanitizing earlier tainted components is sufficient. All changes occur in pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go within the shown signAndPostMessage function, and we do not need new imports.

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
@@ -135,7 +135,12 @@
 	postReq.Header.Add("X-Flashbots-Origin", "chainlink")
 	postReq.Header.Add("Content-Type", "application/json")
 
-	reqDesc := fmt.Sprintf("%s %s body: %s", postReq.Method, postReq.URL.String(), string(body))
+	// Sanitize URL and body to avoid log injection via newline or carriage return characters.
+	safeURL := strings.ReplaceAll(postReq.URL.String(), "\n", "")
+	safeURL = strings.ReplaceAll(safeURL, "\r", "")
+	safeBody := strings.ReplaceAll(string(body), "\n", "")
+	safeBody = strings.ReplaceAll(safeBody, "\r", "")
+	reqDesc := fmt.Sprintf("%s %s body: %s", postReq.Method, safeURL, safeBody)
 	resp, err := http.DefaultClient.Do(postReq)
 	if err != nil {
 		return nil, fmt.Errorf("request %s failed: %w", reqDesc, err)
EOF
@@ -135,7 +135,12 @@
postReq.Header.Add("X-Flashbots-Origin", "chainlink")
postReq.Header.Add("Content-Type", "application/json")

reqDesc := fmt.Sprintf("%s %s body: %s", postReq.Method, postReq.URL.String(), string(body))
// Sanitize URL and body to avoid log injection via newline or carriage return characters.
safeURL := strings.ReplaceAll(postReq.URL.String(), "\n", "")
safeURL = strings.ReplaceAll(safeURL, "\r", "")
safeBody := strings.ReplaceAll(string(body), "\n", "")
safeBody = strings.ReplaceAll(safeBody, "\r", "")
reqDesc := fmt.Sprintf("%s %s body: %s", postReq.Method, safeURL, safeBody)
resp, err := http.DefaultClient.Do(postReq)
if err != nil {
return nil, fmt.Errorf("request %s failed: %w", reqDesc, err)
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)?

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