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 239d1fc
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 139 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 BloxrouteBundleSender struct {
c *http.Client
endpoint string
auth string
flashbotKey *ecdsa.PrivateKey
enabledBuilders []BlxrBuilder
}

mevBuilders := make(map[Builder]string)
if opts.builderBloxroute {
mevBuilders[BuilderBloxroute] = ""
// NewBloxrouteBundleSender 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 NewBloxrouteBundleSender(
c *http.Client,
endpoint, auth string,
flashbotKey *ecdsa.PrivateKey,
enabledBuilders ...BlxrBuilder,
) *BloxrouteBundleSender {
return &BloxrouteBundleSender{
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 *BloxrouteBundleSender) Send(
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
24 changes: 23 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,21 @@ const (
ETHSendBundleMethod = "eth_sendBundle"
)

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

var (
_ IBundleSender = &FlashbotBundleSender{}
_ IBundleSender = &BundleSender{}
_ IBundleSender = &BloxrouteBundleSender{}
)

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 +49,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
125 changes: 84 additions & 41 deletions pkg/mev/send_bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,113 @@ 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)
// FlashbotBundleSender https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle,
type FlashbotBundleSender struct {
c *http.Client
key *ecdsa.PrivateKey
endpoint string
}

func NewFlashbotSender(
c *http.Client,
key *ecdsa.PrivateKey,
endpoint string,
) *FlashbotBundleSender {
return &FlashbotBundleSender{
c: c,
key: key,
endpoint: endpoint,
}
}

func (s *FlashbotBundleSender) Send(
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)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("sign flashbot request error: %w", err)
}
signature, err := signRequest(s.key, reqBody)
if err != nil {
return SendBundleResponse{}, fmt.Errorf("sign flashbot request error: %w", err)
}
flashbotSig := fmt.Sprintf("%s:%s",
crypto.PubkeyToAddress(s.key.PublicKey), hexutil.Encode(signature))

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)
}

resp, err := doRequest[SendBundleResponse](
s.c,
httpReq,
[2]string{"X-Flashbots-Signature", flashbotSig})
if err != nil {
return SendBundleResponse{}, err
}

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

return resp, nil
}

// BundleSender https://beaverbuild.org/docs.html; https://rsync-builder.xyz/docs
type BundleSender struct {
c *http.Client
endpoint string
}

func NewBundlerSender(c *http.Client, endpoint string) *BundleSender {
return &BundleSender{
c: c,
endpoint: endpoint,
}
}

func (s *BundleSender) Send(
ctx context.Context, blockNumber uint64, txs ...*types.Transaction,
) (SendBundleResponse, error) {
req := SendBundleRequest{
ID: SendBundleID,
JSONRPC: JSONRPC2,
Method: ETHSendBundleMethod,
}
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)
}

// for flashbot only
headers["X-Flashbots-Signature"] = fmt.Sprintf("%s:%s",
crypto.PubkeyToAddress(opts.flashbotSignKey.PublicKey), hexutil.Encode(signature))
headers := map[string]string{
"Content-Type": "application/json",
"Accept": "application/json",
}
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)
if err != nil {
return SendBundleResponse{}, err
}
Expand All @@ -84,22 +143,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
Loading

0 comments on commit 239d1fc

Please sign in to comment.