Skip to content

Commit

Permalink
refactor: ♻️ mev: BundleSender
Browse files Browse the repository at this point in the history
Signed-off-by: thanhpp <[email protected]>
  • Loading branch information
thanhpp committed Jan 4, 2024
1 parent 9d025fa commit 30e9352
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 140 deletions.
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"
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 {
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)
}

0 comments on commit 30e9352

Please sign in to comment.