Skip to content

Commit

Permalink
feat: ✨ mev: cancel bundle
Browse files Browse the repository at this point in the history
Signed-off-by: thanhpp <[email protected]>
  • Loading branch information
thanhpp committed Jan 11, 2024
1 parent 66f2ff2 commit ed30c14
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 27 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21

require (
github.com/ethereum/go-ethereum v1.13.1
github.com/google/uuid v1.3.0
github.com/shopspring/decimal v1.3.1
github.com/sourcegraph/conc v0.3.0
github.com/stretchr/testify v1.8.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ import (
"net/http"
"strconv"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
)

type BlxrBuilder string
Expand Down Expand Up @@ -51,9 +49,15 @@ func NewBloxrouteClient(
}

func (s *BloxrouteClient) SendBundle(
ctx context.Context, blockNumber uint64, txs ...*types.Transaction,
ctx context.Context,
uuid *string,
blockNumber uint64,
txs ...*types.Transaction,
) (SendBundleResponse, error) {
p := new(BLXRSubmitBundleParams).SetBlockNumber(blockNumber).SetTransactions(txs...)
if uuid != nil {
p.SetUUID(*uuid)
}

mevBuilders := make(map[BlxrBuilder]string)
for _, b := range s.enabledBuilders {
Expand Down Expand Up @@ -96,6 +100,17 @@ func (s *BloxrouteClient) SendBundle(
return SendBundleResponse(resp), nil
}

func (s *BloxrouteClient) CancelBundle(
ctx context.Context, bundleUUID string,
) error {
_, err := s.SendBundle(ctx, &bundleUUID, 0)
if err != nil {
return fmt.Errorf("cancel by send bundle error: %w", err)
}

return nil
}

type BLXRSubmitBundleRequest struct {
ID string `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Expand All @@ -113,6 +128,10 @@ type BLXRSubmitBundleParams struct {
}

func (p *BLXRSubmitBundleParams) SetTransactions(txs ...*types.Transaction) *BLXRSubmitBundleParams {
if len(txs) == 0 {
return p
}

transactions := make([]string, 0, len(txs))
for _, tx := range txs {
transactions = append(transactions, "0x"+txToRlp(tx))
Expand All @@ -124,11 +143,21 @@ func (p *BLXRSubmitBundleParams) SetTransactions(txs ...*types.Transaction) *BLX
}

func (p *BLXRSubmitBundleParams) SetBlockNumber(block uint64) *BLXRSubmitBundleParams {
if block == 0 {
return p
}

p.BlockNumber = fmt.Sprintf("0x%x", block)

return p
}

func (p *BLXRSubmitBundleParams) SetUUID(uuid string) *BLXRSubmitBundleParams {
p.UUID = uuid

return p
}

type BLXRSubmitBundleResponse struct {
Jsonrpc string `json:"jsonrpc,omitempty"`
ID int `json:"id,string,omitempty"`
Expand Down Expand Up @@ -156,12 +185,10 @@ func bloxrouteSignFlashbot(key *ecdsa.PrivateKey, p *BLXRSubmitBundleParams) (st
return "", fmt.Errorf("marshal json error: %w", err)
}

signature, err := signRequest(key, reqBody)
signature, err := requestSignature(key, reqBody)
if err != nil {
return "", fmt.Errorf("sign request error: %w", err)
}

sig := fmt.Sprintf("%s:%s", crypto.PubkeyToAddress(key.PublicKey), hexutil.Encode(signature))

return sig, nil
return signature, nil
}
118 changes: 102 additions & 16 deletions pkg/mev/send_bundle.go → pkg/mev/bundle_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,29 +17,42 @@ import (
// Client https://beaverbuild.org/docs.html; https://rsync-builder.xyz/docs;
// https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle
type Client struct {
c *http.Client
endpoint string
flashbotKey *ecdsa.PrivateKey
c *http.Client
endpoint string
flashbotKey *ecdsa.PrivateKey
cancelBySendBundle bool
}

// NewClient set the flashbotKey to nil will skip adding the signature header.
func NewClient(c *http.Client, endpoint string, flashbotKey *ecdsa.PrivateKey) *Client {
func NewClient(
c *http.Client,
endpoint string,
flashbotKey *ecdsa.PrivateKey,
cancelBySendBundle bool,
) *Client {
return &Client{
c: c,
endpoint: endpoint,
flashbotKey: flashbotKey,
c: c,
endpoint: endpoint,
flashbotKey: flashbotKey,
cancelBySendBundle: cancelBySendBundle,
}
}

func (s *Client) SendBundle(
ctx context.Context, blockNumber uint64, txs ...*types.Transaction,
ctx context.Context,
uuid *string,
blockNumber uint64,
txs ...*types.Transaction,
) (SendBundleResponse, error) {
req := SendBundleRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendBundleMethod,
}
p := new(SendBundleParams).SetBlockNumber(blockNumber).SetTransactions(txs...)
if uuid != nil {
p.SetUUID(*uuid)
}
req.Params = append(req.Params, p)

reqBody, err := json.Marshal(req)
Expand All @@ -49,14 +62,11 @@ func (s *Client) SendBundle(

var headers [][2]string
if s.flashbotKey != nil {
signature, err := signRequest(s.flashbotKey, reqBody)
signature, err := requestSignature(s.flashbotKey, reqBody)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("sign flashbot request error: %w", err)
}
flashbotSig := fmt.Sprintf("%s:%s",
crypto.PubkeyToAddress(s.flashbotKey.PublicKey), hexutil.Encode(signature))

headers = append(headers, [2]string{"X-Flashbots-Signature", flashbotSig})
headers = append(headers, [2]string{"X-Flashbots-Signature", signature})
}

httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.endpoint, bytes.NewBuffer(reqBody))
Expand All @@ -77,14 +87,67 @@ func (s *Client) SendBundle(
return resp, nil
}

func signRequest(key *ecdsa.PrivateKey, body []byte) ([]byte, error) {
func (s *Client) CancelBundle(
ctx context.Context, bundleUUID string,
) error {
if s.cancelBySendBundle {
if _, err := s.SendBundle(ctx, &bundleUUID, 0); err != nil {
return fmt.Errorf("cancel by send bundle error: %w", err)
}
return nil
}

// build request
p := CancelBundleParams{
ReplacementUUID: bundleUUID,
}
req := SendBundleRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHCancelBundleMethod,
Params: []any{p},
}
reqBody, err := json.Marshal(req)
if err != nil {
return fmt.Errorf("marshal json error: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.endpoint, bytes.NewBuffer(reqBody))
if err != nil {
return fmt.Errorf("new http request error: %w", err)
}

var headers [][2]string
if s.flashbotKey != nil {
signature, err := requestSignature(s.flashbotKey, reqBody)
if err != nil {
return fmt.Errorf("sign flashbot request error: %w", err)
}

headers = append(headers, [2]string{"X-Flashbots-Signature", signature})
}

// do
resp, err := doRequest[SendBundleResponse](s.c, httpReq, headers...)
if err != nil {
return err
}

// check
if len(resp.Error.Messange) != 0 {
return fmt.Errorf("response error, code: [%d], message: [%s]", resp.Error.Code, resp.Error.Messange)
}

return nil
}

func requestSignature(key *ecdsa.PrivateKey, body []byte) (string, error) {
hashed := crypto.Keccak256Hash(body).Hex()
signature, err := crypto.Sign(accounts.TextHash([]byte(hashed)), key)
if err != nil {
return nil, fmt.Errorf("sign crypto error: %w", err)
return "", fmt.Errorf("sign crypto error: %w", err)
}

return signature, nil
return fmt.Sprintf("%s:%s", crypto.PubkeyToAddress(key.PublicKey), hexutil.Encode(signature)), nil
}

type SendBundleRequest struct {
Expand All @@ -105,9 +168,17 @@ type SendBundleParams struct {
MaxTimestamp *uint64 `json:"maxTimestamp,omitempty"`
// (Optional) Array[String], A list of tx hashes that are allowed to revert
RevertingTxs *[]string `json:"revertingTxHashes,omitempty"`
// (Optional) String, UUID that can be used to cancel/replace this bundle
ReplacementUUID string `json:"ReplacementUuid,omitempty"`
// (Optional) String, UUID that can be used to cancel/replace this bundle (For beaverbuild)
UUID string `json:"uuid,omitempty"`
}

func (p *SendBundleParams) SetTransactions(txs ...*types.Transaction) *SendBundleParams {
if len(txs) == 0 {
return p
}

transactions := make([]string, 0, len(txs))
for _, tx := range txs {
transactions = append(transactions, "0x"+txToRlp(tx))
Expand All @@ -119,7 +190,22 @@ func (p *SendBundleParams) SetTransactions(txs ...*types.Transaction) *SendBundl
}

func (p *SendBundleParams) SetBlockNumber(block uint64) *SendBundleParams {
if block == 0 {
return p
}

p.BlockNumber = fmt.Sprintf("0x%x", block)

return p
}

func (p *SendBundleParams) SetUUID(uuid string) *SendBundleParams {
p.ReplacementUUID = uuid
p.UUID = uuid

return p
}

type CancelBundleParams struct {
ReplacementUUID string `json:"replacementUuid"`
}
10 changes: 7 additions & 3 deletions pkg/mev/send_bundle_test.go → pkg/mev/bundle_sender_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -56,11 +57,14 @@ func TestSendBundle(t *testing.T) {

t.Log("new tx", signedTx.Hash().String())

sender := mev.NewClient(client, endpoint, privateKey)
resp, err := sender.SendBundle(ctx, blockNumber+12, signedTx)
require.NoError(t, err) // sepolia: code: [-32000], message: [internal server error]
uuid := uuid.NewString()
sender := mev.NewClient(client, endpoint, privateKey, false)

resp, err := sender.SendBundle(ctx, &uuid, blockNumber+12, signedTx)
require.NoError(t, err) // sepolia: code: [-32000], message: [internal server error]
t.Log("send bundle response", resp)

require.NoError(t, sender.CancelBundle(ctx, uuid))
}

func TestUnmarshal(t *testing.T) {
Expand Down
11 changes: 10 additions & 1 deletion pkg/mev/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ const (
SendBundleID = 1
BloxrouteSubmitBundleMethod = "blxr_submit_bundle"
ETHSendBundleMethod = "eth_sendBundle"
ETHCancelBundleMethod = "eth_cancelBundle"
)

type IBundleSender interface {
SendBundle(ctx context.Context, blockNumber uint64, tx ...*types.Transaction) (SendBundleResponse, error)
SendBundle(
ctx context.Context,
uuid *string,
blockNumber uint64,
tx ...*types.Transaction,
) (SendBundleResponse, error)
CancelBundle(
ctx context.Context, bundleUUID string,
) error
}

var (
Expand Down

0 comments on commit ed30c14

Please sign in to comment.