Conversation
|
7f1a37b to
6fe2367
Compare
60f915c to
a1d5fa8
Compare
pszal
left a comment
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
I don't see revertMode, only canRevert:
https://docs.flashbots.net/flashbots-mev-share/searchers/understanding-bundles#bundle-definition
There was a problem hiding this comment.
Indeed, but I followed the spec they gave me in our conversation.
There was a problem hiding this comment.
Does it make sense to document this spec somewhere (e.g., doc.go)?
There was a problem hiding this comment.
Added a link to the docs.
There was a problem hiding this comment.
Pull request overview
This PR adds optional Flashbots bundle broadcasting to the TXM dual-broadcast client path, gated by a new Bundles config flag. When enabled, after successfully sending a transaction to the Flashbots endpoint, the client builds and submits a bundle composed of in-flight (unconfirmed) transactions.
Changes:
- Add
Bundlesconfiguration for TransactionManagerV2 (TOML + docs + config interfaces). - Extend
txm.TxStorewith APIs needed for bundle building (fetch unconfirmed txs; update signed attempts). - Implement Flashbots bundle creation + submission and add unit tests for URL param parsing / bundle request construction.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/txmgr/builder.go | Passes Bundles config into dual-broadcast client selection. |
| pkg/txm/txm.go | Extends TxStore interface with FetchUnconfirmedTransactions and UpdateSignedAttempt. |
| pkg/txm/storage/inmemory_store_manager.go | Adds store-manager forwarding method for fetching all unconfirmed txs. |
| pkg/txm/storage/inmemory_store.go | Adds FetchUnconfirmedTransactions() (deep-copy + nonce sort). |
| pkg/txm/mock_tx_store_test.go | Updates mock store to satisfy new TxStore interface methods. |
| pkg/txm/clientwrappers/dualbroadcast/selector.go | Threads bundles flag and tx store into Flashbots client constructor. |
| pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go | Implements bundle send flow, request signing/posting changes, URL param parsing. |
| pkg/txm/clientwrappers/dualbroadcast/flashbots_client_test.go | Adds tests for URL param parsing and bundle request composition. |
| pkg/config/toml/testdata/config-full.toml | Adds Bundles to the full TOML test config. |
| pkg/config/toml/docs.toml | Documents Bundles TOML setting. |
| pkg/config/toml/config_test.go | Updates config tests to include Bundles defaults/docs handling. |
| pkg/config/toml/config.go | Adds Bundles field to TransactionManagerV2Config and merges it in setFrom. |
| pkg/config/config.go | Extends TransactionManagerV2 interface with Bundles(). |
| pkg/config/chain_scoped_transactions.go | Implements Bundles() accessor for chain-scoped config wrapper. |
| CONFIG.md | Adds Bundles to user-facing configuration documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // After successfully sending the transaction, send a bundle with all unconfirmed transactions | ||
| // 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) |
There was a problem hiding this comment.
The PR description says bundle broadcasting is “fire and forget”, but this call is synchronous: SendTransaction will block until SendBundle completes (including an extra BlockByNumber RPC + HTTP request). Consider running SendBundle asynchronously (e.g., in a goroutine with its own bounded context) so transaction broadcasting latency isn’t increased and bundle failures can’t delay TXM.
| // Sort by nonce in ascending order | ||
| sort.Slice(transactions, func(i, j int) bool { | ||
| return *transactions[i].Nonce < *transactions[j].Nonce | ||
| }) |
There was a problem hiding this comment.
FetchUnconfirmedTransactions adds nonce-sorting and returns an error on nil nonces, but there are no unit tests covering this new behavior. Add tests to validate sorted order and the nil-nonce error so the bundle logic can rely on it safely.
| nonce, err := hexutil.DecodeUint64(response) | ||
| var resultStr string | ||
| if err := json.Unmarshal(raw, &resultStr); err != nil { | ||
| return 0, fmt.Errorf("failed to unmarshal response into string: %w", err) |
There was a problem hiding this comment.
I see that you log arg when it fails in other places, any reason to not log raw here?
There was a problem hiding this comment.
No reason, I added raw as well.
| for _, unconfirmedTx := range unconfirmedTxs { | ||
| if len(unconfirmedTx.Attempts) > 0 { | ||
| attempts = append(attempts, unconfirmedTx.Attempts[len(unconfirmedTx.Attempts)-1]) | ||
| if unconfirmedTx.Nonce != nil { |
There was a problem hiding this comment.
if you check for that here, then it makes sense to check or guarantee in the if above that attempts and nonces are consistent (i.e., you append to both or neither).
| refundRaw := values.Get("refund") | ||
| if refundRaw != "" { | ||
| parts := strings.Split(refundRaw, ":") | ||
| if len(parts) == 2 { |
There was a problem hiding this comment.
is it ok if its not 2? shouldn't we err?
| if err := json.Unmarshal(raw, &bundleResult); err != nil { | ||
| return fmt.Errorf("failed to decode response %v into bundle result: %w", raw, err) | ||
| } | ||
| d.lggr.Infow("Broadcasted transaction bundle", "nonces", nonces, "bundleHash", bundleResult.BundleHash) |
There was a problem hiding this comment.
is it intentional to log only nonces and not the actual bundle (or at least IDs of its txs)?
There was a problem hiding this comment.
Yes. Logs can be very noisy in Core, so the idea here is avoid replicating information that can be recovered by past logs. Similar to how we confirm transactions, we use nonce as an identifier to retrieve information if needed, as the individual transaction details should be available a few lines before this message. I'll add the IDs though to make it cleaner.
| // 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.Errorw("error sending bundle", "err", err) |
Check failure
Code scanning / CodeQL
Log entries created from user input High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI about 2 months ago
In general, to fix “log entries created from user input,” any user-influenced string that is written to logs should be sanitized/encoded so it cannot inject new log entries or markup. For plain-text logs, that usually means removing or replacing \n and \r. For HTML-rendered logs, it also means HTML-escaping. In this codebase, the flagged sink is a structured logger call that logs an error value; we can mitigate the issue by converting the error to a string and stripping line breaks (and optionally carriage returns) before logging.
The best minimal fix without changing existing functionality is to sanitize the err value only at the point where CodeQL flags the problem, by converting err to a string, removing \n and \r, and logging that sanitized string instead of the raw error. This keeps the log message content almost identical (minus dangerous control characters) while eliminating the risk of log forging via embedded line breaks. We can use the already-imported strings package, so no new imports are required.
Concretely, in pkg/txm/clientwrappers/dualbroadcast/flashbots_client.go, around line 111–114:
if d.bundles {
if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
d.lggr.Errorw("error sending bundle", "err", err)
}
}we will introduce a small sanitization step:
if d.bundles {
if err := d.SendBundle(ctx, tx.FromAddress, params); err != nil {
sanitizedErr := strings.ReplaceAll(err.Error(), "\n", "")
sanitizedErr = strings.ReplaceAll(sanitizedErr, "\r", "")
d.lggr.Errorw("error sending bundle", "err", sanitizedErr)
}
}This uses strings.ReplaceAll (already imported) and keeps all other logic intact.
| @@ -110,7 +110,9 @@ | ||
| // 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.Errorw("error sending bundle", "err", err) | ||
| sanitizedErr := strings.ReplaceAll(err.Error(), "\n", "") | ||
| sanitizedErr = strings.ReplaceAll(sanitizedErr, "\r", "") | ||
| d.lggr.Errorw("error sending bundle", "err", sanitizedErr) | ||
| } | ||
| } | ||
| return nil |
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 last attempt of each transaction. The bundle broadcasting is a fire and forget process that helps nonce unblocking, so it is not tracked.