From 30e935240b70f09bc2fb251fdcc182575d52e829 Mon Sep 17 00:00:00 2001 From: thanhpp Date: Thu, 4 Jan 2024 10:16:16 +0700 Subject: [PATCH] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20mev:=20BundleS?= =?UTF-8?q?ender?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: thanhpp --- pkg/mev/bloxroute_submit_bundle.go | 146 +++++++++++------------------ pkg/mev/pkg.go | 23 ++++- pkg/mev/send_bundle.go | 71 ++++++-------- pkg/mev/send_bundle_test.go | 11 +-- 4 files changed, 111 insertions(+), 140 deletions(-) diff --git a/pkg/mev/bloxroute_submit_bundle.go b/pkg/mev/bloxroute_submit_bundle.go index 0454ac8..68ff816 100644 --- a/pkg/mev/bloxroute_submit_bundle.go +++ b/pkg/mev/bloxroute_submit_bundle.go @@ -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 } @@ -89,47 +96,6 @@ 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"` @@ -137,13 +103,13 @@ type BLXRSubmitBundleRequest struct { } 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 { diff --git a/pkg/mev/pkg.go b/pkg/mev/pkg.go index 15ed1aa..8b42f2e 100644 --- a/pkg/mev/pkg.go +++ b/pkg/mev/pkg.go @@ -2,6 +2,7 @@ package mev import ( "bytes" + "context" "encoding/hex" "encoding/json" "fmt" @@ -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) @@ -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) diff --git a/pkg/mev/send_bundle.go b/pkg/mev/send_bundle.go index b2194ef..593cc5b 100644 --- a/pkg/mev/send_bundle.go +++ b/pkg/mev/send_bundle.go @@ -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 } @@ -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"` diff --git a/pkg/mev/send_bundle_test.go b/pkg/mev/send_bundle_test.go index f1ab724..1aa1963 100644 --- a/pkg/mev/send_bundle_test.go +++ b/pkg/mev/send_bundle_test.go @@ -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 @@ -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) }