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

refactor: ♻️ mev: BundleSender #17

Merged
merged 1 commit into from
Jan 4, 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
146 changes: 56 additions & 90 deletions pkg/mev/bloxroute_submit_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,69 +14,76 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

type Builder string
type BlxrBuilder string

const (
BuilderBloxroute Builder = "bloxroute"
BuilderFlashbot Builder = "flashbots"
BuilderBeaverBuild Builder = "beaverbuild"
BuilderRsyncBuilder Builder = "rsync-builder"
BuilderAll Builder = "all"
BuilderBloxroute BlxrBuilder = "bloxroute"
thanhpp marked this conversation as resolved.
Show resolved Hide resolved
BuilderFlashbot BlxrBuilder = "flashbots"
BuilderBeaverBuild BlxrBuilder = "beaverbuild"
BuilderRsyncBuilder BlxrBuilder = "rsync-builder"
BuilderAll BlxrBuilder = "all"
)

// BloxrouteSubmitBundle https://docs.bloxroute.com/apis/mev-solution/bundle-submission
func BloxrouteSubmitBundle( // nolint: cyclop
ctx context.Context, c *http.Client, auth, endpoint string,
param *BLXRSubmitBundleParams, options ...BloxrouteSubmitBundleOption,
) (SendBundleResponse, error) {
var opts blxrSubmitBundleOptions
for _, fn := range options {
if fn == nil {
continue
}
fn(&opts)
}
type BloxrouteClient struct {
c *http.Client
endpoint string
auth string
flashbotKey *ecdsa.PrivateKey
enabledBuilders []BlxrBuilder
}

mevBuilders := make(map[Builder]string)
if opts.builderBloxroute {
mevBuilders[BuilderBloxroute] = ""
// NewBloxrouteClient set flashbotKey to nil if you don't want to send to flashbot builders
// With BuilderAll still need to add the flashbot key & the flashbot builder separately
// https://docs.bloxroute.com/apis/mev-solution/bundle-submission
func NewBloxrouteClient(
c *http.Client,
endpoint, auth string,
flashbotKey *ecdsa.PrivateKey,
enabledBuilders ...BlxrBuilder,
) *BloxrouteClient {
return &BloxrouteClient{
c: c,
endpoint: endpoint,
auth: auth,
flashbotKey: flashbotKey,
enabledBuilders: enabledBuilders,
}
if opts.builderFlashbot != nil {
sig, err := bloxrouteSignFlashbot(opts.builderFlashbot, param)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("sign flashbot error: %w", err)
}

func (s *BloxrouteClient) SendBundle(
ctx context.Context, blockNumber uint64, txs ...*types.Transaction,
) (SendBundleResponse, error) {
p := new(BLXRSubmitBundleParams).SetBlockNumber(blockNumber).SetTransactions(txs...)

mevBuilders := make(map[BlxrBuilder]string)
for _, b := range s.enabledBuilders {
if b == BuilderFlashbot && s.flashbotKey != nil {
thanhpp marked this conversation as resolved.
Show resolved Hide resolved
sig, err := bloxrouteSignFlashbot(s.flashbotKey, p)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("sign flashbot error: %w", err)
}
mevBuilders[BuilderFlashbot] = sig
continue
}
mevBuilders[BuilderFlashbot] = sig
}
if opts.builderBeaverBuild {
mevBuilders[BuilderBeaverBuild] = ""
}
if opts.builderRsyncBuilder {
mevBuilders[BuilderRsyncBuilder] = ""
}
if opts.builderAll {
mevBuilders[BuilderAll] = ""
mevBuilders[b] = ""
}
param.MevBuilders = mevBuilders

p.MEVBuilders = mevBuilders
req := BLXRSubmitBundleRequest{
ID: strconv.Itoa(SendBundleID),
Method: BloxrouteSubmitBundleMethod,
Params: param,
Params: p,
}
reqBody, err := json.Marshal(req)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("marshal json error: %w", err)
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(reqBody))
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.endpoint, bytes.NewBuffer(reqBody))
if err != nil {
return SendBundleResponse{}, fmt.Errorf("new http request error: %w", err)
}
httpReq.Header.Add("Authorization", auth)
httpReq.Header.Add("Content-Type", "application/json")
httpReq.Header.Add("Accept", "application/json")

resp, err := doRequest[SendBundleResponse](c, httpReq)
resp, err := doRequest[SendBundleResponse](s.c, httpReq, [2]string{"Authorization", s.auth})
if err != nil {
return SendBundleResponse{}, err
}
Expand All @@ -89,61 +96,20 @@ func BloxrouteSubmitBundle( // nolint: cyclop
return resp, nil
}

type blxrSubmitBundleOptions struct {
builderBloxroute bool
builderFlashbot *ecdsa.PrivateKey
builderBeaverBuild bool
builderRsyncBuilder bool
builderAll bool
}

type BloxrouteSubmitBundleOption func(*blxrSubmitBundleOptions)

func WithBuilderBloxroute() BloxrouteSubmitBundleOption {
return func(bsbo *blxrSubmitBundleOptions) {
bsbo.builderBloxroute = true
}
}

func WithBuilderFlashbot(key *ecdsa.PrivateKey) BloxrouteSubmitBundleOption {
return func(bsbo *blxrSubmitBundleOptions) {
bsbo.builderFlashbot = key
}
}

func WithBuilderBeaverBuild() BloxrouteSubmitBundleOption {
return func(bsbo *blxrSubmitBundleOptions) {
bsbo.builderBeaverBuild = true
}
}

func WithBuilderRsyncBuilder() BloxrouteSubmitBundleOption {
return func(bsbo *blxrSubmitBundleOptions) {
bsbo.builderRsyncBuilder = true
}
}

// WithBuilderAll still need to use the WithBuilderFlashbot to submit to flashbots.
func WithBuilderAll() BloxrouteSubmitBundleOption {
return func(bsbo *blxrSubmitBundleOptions) {
bsbo.builderAll = true
}
}

type BLXRSubmitBundleRequest struct {
ID string `json:"id,omitempty"`
Method string `json:"method,omitempty"`
Params *BLXRSubmitBundleParams `json:"params,omitempty"`
}

type BLXRSubmitBundleParams struct {
Transaction []string `json:"transaction,omitempty"`
BlockNumber string `json:"block_number,omitempty"`
MinTimestamp *uint64 `json:"min_timestamp,omitempty"`
MaxTimestamp *uint64 `json:"max_timestamp,omitempty"`
RevertingHashes *[]string `json:"reverting_hashes,omitempty"`
UUID string `json:"uuid,omitempty"`
MevBuilders map[Builder]string `json:"mev_builders,omitempty"`
Transaction []string `json:"transaction,omitempty"`
BlockNumber string `json:"block_number,omitempty"`
MinTimestamp *uint64 `json:"min_timestamp,omitempty"`
MaxTimestamp *uint64 `json:"max_timestamp,omitempty"`
RevertingHashes *[]string `json:"reverting_hashes,omitempty"`
UUID string `json:"uuid,omitempty"`
MEVBuilders map[BlxrBuilder]string `json:"mev_builders,omitempty"`
}

func (p *BLXRSubmitBundleParams) SetTransactions(txs ...*types.Transaction) *BLXRSubmitBundleParams {
Expand Down
23 changes: 22 additions & 1 deletion pkg/mev/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mev

import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"fmt"
Expand All @@ -18,6 +19,20 @@ const (
ETHSendBundleMethod = "eth_sendBundle"
)

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

var (
_ IBundleSender = &Client{}
_ IBundleSender = &BloxrouteClient{}
)

var defaultHeaders = [][2]string{ // nolint: gochecknoglobals
{"Content-Type", "application/json"},
{"Accept", "application/json"},
}

func txToRlp(tx *types.Transaction) string {
var buff bytes.Buffer
_ = tx.EncodeRLP(&buff)
Expand All @@ -33,9 +48,15 @@ func txToRlp(tx *types.Transaction) string {
return rlp
}

func doRequest[T any](c *http.Client, req *http.Request) (T, error) {
func doRequest[T any](c *http.Client, req *http.Request, headers ...[2]string) (T, error) {
var t T

for _, h := range defaultHeaders {
req.Header.Add(h[0], h[1])
}
for _, h := range headers {
req.Header.Add(h[0], h[1])
}
httpResp, err := c.Do(req)
if err != nil {
return t, fmt.Errorf("do request error: %w", err)
Expand Down
71 changes: 29 additions & 42 deletions pkg/mev/send_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,57 @@ import (
"github.com/ethereum/go-ethereum/crypto"
)

// SendBundle https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle,
// https://beaverbuild.org/docs.html, https://rsync-builder.xyz/docs
func SendBundle( // nolint: cyclop
ctx context.Context, c *http.Client, endpoint string, param *SendBundleParams, options ...SendBundleOption,
) (SendBundleResponse, error) {
var opts sendBundleOpts
for _, fn := range options {
if fn == nil {
continue
}
fn(&opts)
// 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
}

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

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

reqBody, err := json.Marshal(req)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("marshal json error: %w", err)
}

headers := make(map[string]string)
if opts.flashbotSignKey != nil {
signature, err := signRequest(opts.flashbotSignKey, reqBody)
var headers [][2]string
if s.flashbotKey != nil {
signature, err := signRequest(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))

// for flashbot only
headers["X-Flashbots-Signature"] = fmt.Sprintf("%s:%s",
crypto.PubkeyToAddress(opts.flashbotSignKey.PublicKey), hexutil.Encode(signature))
headers = append(headers, [2]string{"X-Flashbots-Signature", flashbotSig})
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint, bytes.NewBuffer(reqBody))

httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, s.endpoint, bytes.NewBuffer(reqBody))
if err != nil {
return SendBundleResponse{}, fmt.Errorf("new http request error: %w", err)
}

for k, v := range headers {
httpReq.Header.Add(k, v)
}
httpReq.Header.Add("Content-Type", "application/json")
httpReq.Header.Add("Accept", "application/json")

resp, err := doRequest[SendBundleResponse](c, httpReq)
resp, err := doRequest[SendBundleResponse](s.c, httpReq, headers...)
if err != nil {
return SendBundleResponse{}, err
}
Expand All @@ -84,22 +87,6 @@ func signRequest(key *ecdsa.PrivateKey, body []byte) ([]byte, error) {
return signature, nil
}

type sendBundleOpts struct {
flashbotSignKey *ecdsa.PrivateKey
}

type SendBundleOption func(*sendBundleOpts)

func WithFlashbotSignature(key *ecdsa.PrivateKey) SendBundleOption {
return func(sbo *sendBundleOpts) {
if key == nil {
return
}

sbo.flashbotSignKey = key
}
}

type SendBundleRequest struct {
ID int `json:"id"`
JSONRPC string `json:"jsonrpc"`
Expand Down
11 changes: 4 additions & 7 deletions pkg/mev/send_bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestSendBundle(t *testing.T) {
t.Skip()
var (
rawKey = "..."
rawKey = "...."
endpoint = "https://relay-sepolia.flashbots.net"
ctx = context.Background()
client = http.DefaultClient
Expand Down Expand Up @@ -55,12 +55,9 @@ func TestSendBundle(t *testing.T) {

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

param := new(mev.SendBundleParams).
SetTransactions(signedTx).
SetBlockNumber(blockNumber + 12)

resp, err := mev.SendBundle(ctx, client, endpoint, param, mev.WithFlashbotSignature(privateKey))
require.NoError(t, err)
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]

t.Log("send bundle response", resp)
}
Loading