Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ mev: cancel bundle #20

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading