Skip to content

Commit

Permalink
Multiple relays, profit switching and parallel request execution (fla…
Browse files Browse the repository at this point in the history
…shbots#51)

* multi-relay support, profit switching, parallel requests

* improve parallel calls
  • Loading branch information
metachris authored Mar 17, 2022
1 parent d5712a7 commit fc34b90
Show file tree
Hide file tree
Showing 9 changed files with 353 additions and 168 deletions.
20 changes: 15 additions & 5 deletions cmd/mev-boost/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package main

import (
"flag"
"math/rand"
"net/http"
"os"
"strconv"
"strings"
"time"

"github.com/flashbots/mev-middleware/lib"
"github.com/sirupsen/logrus"
Expand All @@ -14,21 +17,28 @@ var (
version = "dev" // is set during build process

// defaults
defaultPort = 18550
defaultRelayURL = getEnv("RELAY_URL", "http://127.0.0.1:28545")
defaultPort = 18550
defaultRelayURLs = getEnv("RELAY_URLS", "http://127.0.0.1:28545")

// cli flags
port = flag.Int("port", defaultPort, "port for mev-boost to listen on")
relayURL = flag.String("relayUrl", defaultRelayURL, "url to relay")
port = flag.Int("port", defaultPort, "port for mev-boost to listen on")
relayURLs = flag.String("relayUrl", defaultRelayURLs, "relay urls - single entry or comma-separated list")
)

func main() {
rand.Seed(time.Now().UnixNano()) // warning: not a cryptographically secure seed

flag.Parse()
log := logrus.WithField("prefix", "cmd/mev-boost")
log.Printf("mev-boost %s\n", version)

_relayURLs := []string{}
for _, entry := range strings.Split(*relayURLs, ",") {
_relayURLs = append(_relayURLs, strings.Trim(entry, " "))
}

store := lib.NewStore()
router, err := lib.NewRouter(*relayURL, store, log)
router, err := lib.NewRouter(_relayURLs, store, log)
if err != nil {
panic(err)
}
Expand Down
3 changes: 2 additions & 1 deletion lib/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type rpcRequest struct {
Params []interface{} `json:"params"`
}

func parseRPCResponse(data []byte) (ret rpcResponse, err error) {
func parseRPCResponse(data []byte) (ret *rpcResponse, err error) {
ret = new(rpcResponse)
err = json.Unmarshal(data, &ret)
return
}
4 changes: 2 additions & 2 deletions lib/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
)

// NewRouter creates a json rpc router that handles all methods
func NewRouter(relayURL string, store Store, log *logrus.Entry) (*mux.Router, error) {
relay, err := newRelayService(relayURL, store, log)
func NewRouter(relayURLs []string, store Store, log *logrus.Entry) (*mux.Router, error) {
relay, err := newRelayService(relayURLs, store, log)
if err != nil {
return nil, err
}
Expand Down
57 changes: 43 additions & 14 deletions lib/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,29 +61,29 @@ func TestNewRouter(t *testing.T) {
_, mockHTTPServer := newMockHTTPServer(t, 200, "", "{}", false)

tests := []struct {
name string
relayURL string
wantErr bool
name string
relayURLs []string
wantErr bool
}{
{
"success",
"http://bar",
[]string{"http://bar"},
false,
},
{
"MockHTTPServer success",
mockHTTPServer.URL,
[]string{mockHTTPServer.URL},
false,
},
{
"fails with empty relayURL",
"",
[]string{""},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewRouter(tt.relayURL, NewStore(), logrus.WithField("testing", true))
_, err := NewRouter(tt.relayURLs, NewStore(), logrus.WithField("testing", true))
if (err != nil) != tt.wantErr {
t.Errorf("NewRouter() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -92,12 +92,12 @@ func TestNewRouter(t *testing.T) {
}
}

func formatRequestBody(method string, requestArray []interface{}) ([]byte, error) {
func formatRequestBody(method string, params []interface{}) ([]byte, error) {
return json.Marshal(map[string]interface{}{
"id": "1",
"jsonrpc": "2.0",
"method": method,
"params": requestArray,
"params": params,
})
}

Expand All @@ -122,6 +122,7 @@ type httpTest struct {
name string
requestArray []interface{}
expectedResponseResult interface{}
expectedResponseCheck func(t *testing.T, rpcResp *rpcResponse) // if expectedResponseCheck is provided, expectedResponseResult will be ignored
expectedStatusCode int
mockStatusCode int
expectedRequestsToRelay int
Expand Down Expand Up @@ -157,9 +158,11 @@ func testHTTPMethodWithDifferentRPC(t *testing.T, jsonRPCMethodCaller string, js

if store == nil {
store = NewStore()
store.SetForkchoiceResponse("0x01", mockRelayHTTP.URL, "0x01")
}

// Create the router pointing at the mock server
r, err := NewRouter(mockRelayHTTP.URL, store, logrus.WithField("testing", true))
r, err := NewRouter([]string{mockRelayHTTP.URL}, store, logrus.WithField("testing", true))
require.Nil(t, err, "error creating router")

// Craft a JSON-RPC request to the router
Expand All @@ -171,7 +174,13 @@ func testHTTPMethodWithDifferentRPC(t *testing.T, jsonRPCMethodCaller string, js
r.ServeHTTP(w, req)

if !skipRespCheck {
assert.JSONEq(t, string(resp), w.Body.String(), "expected response to be json equal")
if tt.expectedResponseCheck != nil {
rpcResp, err := parseRPCResponse(w.Body.Bytes())
require.Nil(t, err, "error parsing rpc response")
tt.expectedResponseCheck(t, rpcResp)
} else {
assert.JSONEq(t, string(resp), w.Body.String(), "expected response to be json equal")
}
}
assert.Equal(t, tt.expectedStatusCode, w.Result().StatusCode, "expected status code to be equal")
assert.Equal(t, tt.expectedRequestsToRelay, mockRelay.reqCount, "expected request count to relay to be equal")
Expand All @@ -182,14 +191,28 @@ func strToBytes(s string) *hexutil.Bytes {
ret := hexutil.Bytes(common.Hex2Bytes(s))
return &ret
}

func TestStrToBytes(t *testing.T) {
a := strToBytes("0x1")
b := strToBytes("0x01")
require.Equal(t, a, b)
}

func TestMevService_ForckChoiceUpdated(t *testing.T) {
tests := []httpTest{
{
"basic success",
[]interface{}{catalyst.ForkchoiceStateV1{}, catalyst.PayloadAttributesV1{
SuggestedFeeRecipient: common.HexToAddress("0x0000000000000000000000000000000000000001"),
}},
ForkChoiceResponse{PayloadID: strToBytes("0x1")},
ForkChoiceResponse{PayloadID: strToBytes("0x1"), PayloadStatus: PayloadStatus{Status: ForkchoiceStatusValid}},
func(t *testing.T, rpcResp *rpcResponse) {
var resp ForkChoiceResponse
err := json.Unmarshal(rpcResp.Result, &resp)
require.Nil(t, err, err)
assert.Equal(t, 8, len(*resp.PayloadID))
assert.Equal(t, ForkchoiceStatusValid, resp.PayloadStatus.Status)
},
200,
200,
1,
Expand Down Expand Up @@ -217,6 +240,7 @@ func TestRelayService_ProposeBlindedBlockV1(t *testing.T) {
BaseFeePerGas: big.NewInt(4),
Transactions: &[]string{},
},
nil,
200,
200,
1,
Expand All @@ -232,12 +256,13 @@ func TestRelayervice_GetPayloadHeaderV1(t *testing.T) {
tests := []httpTest{
{
"basic success",
[]interface{}{"0x1"},
[]interface{}{"0x01"},
ExecutionPayloadWithTxRootV1{
BlockHash: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
BaseFeePerGas: big.NewInt(4),
TransactionsRoot: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000002"),
},
nil,
200,
200,
1,
Expand Down Expand Up @@ -268,8 +293,9 @@ func TestRelayervice_GetPayloadAndPropose(t *testing.T) {
{
httpTest{
"get payload and store it",
[]interface{}{"0x1"},
[]interface{}{"0x01"},
payload,
nil,
200,
200,
0,
Expand All @@ -291,6 +317,7 @@ func TestRelayervice_GetPayloadAndPropose(t *testing.T) {
Signature: "0x0000000000000000000000000000000000000000000000000000000000000002",
}},
payload,
nil,
200,
200,
1,
Expand Down Expand Up @@ -325,6 +352,7 @@ func TestRelayervice_GetPayloadAndProposeCamelCase(t *testing.T) {
"get payload and store it",
[]interface{}{"0x1"},
payload,
nil,
200,
200,
0,
Expand All @@ -346,6 +374,7 @@ func TestRelayervice_GetPayloadAndProposeCamelCase(t *testing.T) {
Signature: "0x0000000000000000000000000000000000000000000000000000000000000002",
}},
payload,
nil,
200,
200,
1,
Expand Down
Loading

0 comments on commit fc34b90

Please sign in to comment.