Skip to content

Commit f5a308b

Browse files
odysseus0claude
andauthored
Add cancel-rpc-tx command for E2E cancellation testing (#8)
Implements RPC-based cancellation testing via self-transfer detection pattern. Tests protect-rpc's cancellation flow: sends original tx, then self-transfer with same nonce triggers cancellation via protect-of-api. Mirrors cancel-relay-tx structure for consistent E2E testing patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 24846dc commit f5a308b

File tree

2 files changed

+65
-1
lines changed

2 files changed

+65
-1
lines changed

cmd/mevrepl/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,14 @@ func main() {
211211
Action: handler.CancelRelayTx(ctx),
212212
}
213213

214+
cancelRpcTxCmd := &cli.Command{
215+
Name: "cancel-rpc-tx",
216+
Aliases: []string{"crpc"},
217+
Description: "cancel private tx via RPC using self-transfer detection",
218+
Flags: txFlags,
219+
Action: handler.CancelRpcTx(ctx),
220+
}
221+
214222
// This command calls Flashbots Protect Transaction Status API and logs info txStatus
215223
//
216224
// For details, see: https://docs.flashbots.net/flashbots-protect/additional-documentation/status-api
@@ -260,7 +268,7 @@ func main() {
260268
Action: handler.Backrun(ctx),
261269
}
262270

263-
app.Commands = append(app.Commands, sendPrivateTxCmd, sendFakeTxCmd, txStatusCmd, hintsStreamCmd, backrunCmd, sendPrivateRelayTxCmd, ethCallBundleCmd, ethSendBundleCmd, cancelRelayTxCmd, ethCancelBundleCmd)
271+
app.Commands = append(app.Commands, sendPrivateTxCmd, sendFakeTxCmd, txStatusCmd, hintsStreamCmd, backrunCmd, sendPrivateRelayTxCmd, ethCallBundleCmd, ethSendBundleCmd, cancelRelayTxCmd, cancelRpcTxCmd, ethCancelBundleCmd)
264272

265273
if err := app.Run(os.Args); err != nil {
266274
panic(err)

ports/handler.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,62 @@ func (h *Handler) CancelRelayTx(ctx context.Context) func(*cli.Context) error {
301301
}
302302
}
303303

304+
func (h *Handler) CancelRpcTx(ctx context.Context) func(*cli.Context) error {
305+
return func(cCtx *cli.Context) error {
306+
ethAmountStr := cCtx.String("eth-amount")
307+
txTypeStr := TestTxType(cCtx.String("tx-type"))
308+
toAddr, err := h.parseToAddr(txTypeStr)
309+
if err != nil {
310+
return err
311+
}
312+
313+
ethAmount, err := h.getEthValue(ethAmountStr)
314+
if err != nil {
315+
return err
316+
}
317+
318+
senderAddr := crypto.PubkeyToAddress(h.Alice.PublicKey)
319+
nonce, err := h.MEVClient.FlashbotsRPC.PendingNonceAt(ctx, senderAddr)
320+
if err != nil {
321+
return fmt.Errorf("failed to get pending nonce: %w", err)
322+
}
323+
324+
tx, err := h.ethTransfer(ctx, ethAmount, h.DefaultPriorityFee, nil, toAddr, &nonce)
325+
if err != nil {
326+
return fmt.Errorf("failed to construct ethTransfer tx error %w", err)
327+
}
328+
329+
_, err = h.sendPrivateTx(ctx, tx)
330+
if err != nil {
331+
return fmt.Errorf("failed to send private tx error %w", err)
332+
}
333+
334+
slog.Info("Sent private transaction before cancellation", "hash", tx.Hash(), "nonce", nonce)
335+
336+
// protect-rpc detects self-transfer (to == sender && data <= 2 bytes) and cancels original tx with same nonce
337+
cancelTx, err := h.ethTransfer(ctx, big.NewInt(1), h.DefaultPriorityFee, nil, senderAddr, &nonce)
338+
if err != nil {
339+
return fmt.Errorf("failed to construct cancellation tx error %w", err)
340+
}
341+
342+
_, err = h.sendPrivateTx(ctx, cancelTx)
343+
if err != nil {
344+
return fmt.Errorf("failed to send cancellation tx error %w", err)
345+
}
346+
347+
slog.Info("Sent cancellation transaction (self-transfer)", "hash", cancelTx.Hash(), "nonce", nonce)
348+
349+
txStatus, err := h.getFlashbotsTxReceipt(ctx, tx.Hash())
350+
if err != nil {
351+
slog.Warn("Failed to get tx status", "error", err)
352+
} else {
353+
slog.Info("Final tx status", "originalTx", tx.Hash(), "status", txStatus.Status)
354+
}
355+
356+
return nil
357+
}
358+
}
359+
304360
func (h *Handler) sendPrivateRawRelayTx(ctx context.Context, tx *types.Transaction, builders ...string) (*types.Transaction, error) {
305361
resp, err := h.MEVClient.SendPrivateRawRelayTx(ctx, tx, builders...)
306362
if err != nil {

0 commit comments

Comments
 (0)