diff --git a/cli/main.go b/cli/main.go index bc933529..23d6fef5 100644 --- a/cli/main.go +++ b/cli/main.go @@ -2,11 +2,13 @@ package cli import ( "flag" + "math/big" "os" "strconv" "strings" "time" + "github.com/flashbots/go-boost-utils/types" "github.com/flashbots/mev-boost/config" "github.com/flashbots/mev-boost/server" "github.com/sirupsen/logrus" @@ -25,6 +27,7 @@ var ( defaultListenAddr = getEnv("BOOST_LISTEN_ADDR", "localhost:18550") defaultRelayCheck = os.Getenv("RELAY_STARTUP_CHECK") != "" defaultGenesisForkVersion = getEnv("GENESIS_FORK_VERSION", "") + defaultRelayMinBidEth = getEnvFloat64("MIN_BID_ETH", 0) defaultDisableLogVersion = os.Getenv("DISABLE_LOG_VERSION") == "1" // disables adding the version to every log entry // mev-boost relay request timeouts (see also https://github.com/flashbots/mev-boost/issues/287) @@ -46,6 +49,7 @@ var ( listenAddr = flag.String("addr", defaultListenAddr, "listen-address for mev-boost server") relayURLs = flag.String("relays", "", "relay urls - single entry or comma-separated list (scheme://pubkey@host)") relayCheck = flag.Bool("relay-check", defaultRelayCheck, "check relay status on startup and on the status API call") + relayMinBidEth = flag.Float64("min-bid", defaultRelayMinBidEth, "minimum bid to accept from a relay [eth]") relayMonitorURLs = flag.String("relay-monitors", "", "relay monitor urls - single entry or comma-separated list (scheme://host)") relayTimeoutMsGetHeader = flag.Int("request-timeout-getheader", defaultTimeoutMsGetHeader, "timeout for getHeader requests to the relay [ms]") @@ -161,6 +165,23 @@ func Main() { } } + if *relayMinBidEth < 0.0 { + log.Fatal("Please specify a non-negative minimum bid") + } + + if *relayMinBidEth > 1000000.0 { + log.Fatal("Minimum bid is too large, please ensure min-bid is denominated in Ethers") + } + + if *relayMinBidEth > 0.0 { + log.Infof("minimum bid: %v eth", *relayMinBidEth) + } + + relayMinBidWei, err := floatEthTo256Wei(*relayMinBidEth) + if err != nil { + log.WithError(err).Fatal("failed converting min bid") + } + opts := server.BoostServiceOpts{ Log: log, ListenAddr: *listenAddr, @@ -168,6 +189,7 @@ func Main() { RelayMonitors: relayMonitors, GenesisForkVersionHex: genesisForkVersionHex, RelayCheck: *relayCheck, + RelayMinBid: *relayMinBidWei, RequestTimeoutGetHeader: time.Duration(*relayTimeoutMsGetHeader) * time.Millisecond, RequestTimeoutGetPayload: time.Duration(*relayTimeoutMsGetPayload) * time.Millisecond, RequestTimeoutRegVal: time.Duration(*relayTimeoutMsRegVal) * time.Millisecond, @@ -201,3 +223,30 @@ func getEnvInt(key string, defaultValue int) int { } return defaultValue } + +func getEnvFloat64(key string, defaultValue float64) float64 { + if value, ok := os.LookupEnv(key); ok { + val, err := strconv.ParseFloat(value, 64) + if err == nil { + return val + } + } + return defaultValue +} + +// floatEthTo256Wei converts a float (precision 10) denominated in eth to a U256Str denominated in wei +func floatEthTo256Wei(val float64) (*types.U256Str, error) { + weiU256 := new(types.U256Str) + ethFloat := new(big.Float) + weiFloat := new(big.Float) + weiFloatLessPrecise := new(big.Float) + weiInt := new(big.Int) + + ethFloat.SetFloat64(val) + weiFloat.Mul(ethFloat, big.NewFloat(1e18)) + weiFloatLessPrecise.SetString(weiFloat.String()) + weiFloatLessPrecise.Int(weiInt) + + err := weiU256.FromBig(weiInt) + return weiU256, err +} diff --git a/cli/main_test.go b/cli/main_test.go new file mode 100644 index 00000000..0fb87a23 --- /dev/null +++ b/cli/main_test.go @@ -0,0 +1,36 @@ +package cli + +import ( + "math/big" + "testing" + + "github.com/flashbots/go-boost-utils/types" + "github.com/stretchr/testify/require" +) + +func TestFloatEthTo256Wei(t *testing.T) { + // test with small input + i := 0.000000000000012345 + weiU256, err := floatEthTo256Wei(i) + require.NoError(t, err) + require.Equal(t, types.IntToU256(12345), *weiU256) + + // test with zero + i = 0 + weiU256, err = floatEthTo256Wei(i) + require.NoError(t, err) + require.Equal(t, types.IntToU256(0), *weiU256) + + // test with large input + i = 987654.3 + weiU256, err = floatEthTo256Wei(i) + require.NoError(t, err) + + r := big.NewInt(9876543) + r.Mul(r, big.NewInt(1e17)) + referenceWeiU256 := new(types.U256Str) + err = referenceWeiU256.FromBig(r) + require.NoError(t, err) + + require.Equal(t, *referenceWeiU256, *weiU256) +} diff --git a/server/service.go b/server/service.go index 364c39ad..66597186 100644 --- a/server/service.go +++ b/server/service.go @@ -49,6 +49,7 @@ type BoostServiceOpts struct { RelayMonitors []*url.URL GenesisForkVersionHex string RelayCheck bool + RelayMinBid types.U256Str RequestTimeoutGetHeader time.Duration RequestTimeoutGetPayload time.Duration @@ -63,6 +64,7 @@ type BoostService struct { log *logrus.Entry srv *http.Server relayCheck bool + relayMinBid types.U256Str builderSigningDomain types.Domain httpClientGetHeader http.Client @@ -90,6 +92,7 @@ func NewBoostService(opts BoostServiceOpts) (*BoostService, error) { relayMonitors: opts.RelayMonitors, log: opts.Log, relayCheck: opts.RelayCheck, + relayMinBid: opts.RelayMinBid, bids: make(map[bidRespKey]bidResp), builderSigningDomain: builderSigningDomain, @@ -357,9 +360,14 @@ func (m *BoostService) handleGetHeader(w http.ResponseWriter, req *http.Request) log.Warn("ignoring bid with 0 value") return } - log.Debug("bid received") + // Skip if value (fee) is lower than the minimum bid + if responsePayload.Data.Message.Value.Cmp(&m.relayMinBid) == -1 { + log.Debug("ignoring bid below min-bid value") + return + } + mu.Lock() defer mu.Unlock() diff --git a/server/service_test.go b/server/service_test.go index d8cc9413..0356b60c 100644 --- a/server/service_test.go +++ b/server/service_test.go @@ -43,6 +43,7 @@ func newTestBackend(t *testing.T, numRelays int, relayTimeout time.Duration) *te Relays: relayEntries, GenesisForkVersionHex: "0x00000000", RelayCheck: true, + RelayMinBid: types.IntToU256(12345), RequestTimeoutGetHeader: relayTimeout, RequestTimeoutGetPayload: relayTimeout, RequestTimeoutRegVal: relayTimeout, @@ -75,7 +76,7 @@ func (be *testBackend) request(t *testing.T, method, path string, payload any) * func TestNewBoostServiceErrors(t *testing.T) { t.Run("errors when no relays", func(t *testing.T) { - _, err := NewBoostService(BoostServiceOpts{testLog, ":123", []RelayEntry{}, []*url.URL{}, "0x00000000", true, time.Second, time.Second, time.Second}) + _, err := NewBoostService(BoostServiceOpts{testLog, ":123", []RelayEntry{}, []*url.URL{}, "0x00000000", true, types.IntToU256(0), time.Second, time.Second, time.Second}) require.Error(t, err) }) } @@ -367,6 +368,53 @@ func TestGetHeader(t *testing.T) { require.Equal(t, "0xa18385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", resp.Data.Message.Header.BlockHash.String()) }) + t.Run("Respect minimum bid cutoff", func(t *testing.T) { + // Create backend and register relay. + backend := newTestBackend(t, 1, time.Second) + + // Relay will return signed response with value 12344. + backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( + 12344, + "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + ) + + // Run the request. + rr := backend.request(t, http.MethodGet, path, nil) + + // Each relay must have received the request. + require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) + + // Request should have no content (min bid is 12345) + require.Equal(t, http.StatusNoContent, rr.Code) + }) + + t.Run("Allow bids which meet minimum bid cutoff", func(t *testing.T) { + // Create backend and register relay. + backend := newTestBackend(t, 1, time.Second) + + // First relay will return signed response with value 12345. + backend.relays[0].GetHeaderResponse = backend.relays[0].MakeGetHeaderResponse( + 12345, + "0xa28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0xe28385e7bd68df656cd0042b74b69c3104b5356ed1f20eb69f1f925df47a3ab7", + "0x8a1d7b8dd64e0aafe7ea7b6c95065c9364cf99d38470c12ee807d55f7de1529ad29ce2c422e0b65e3d5a05c02caca249", + ) + + // Run the request. + rr := backend.request(t, http.MethodGet, path, nil) + + // Each relay must have received the request. + require.Equal(t, 1, backend.relays[0].GetRequestCount(path)) + + // Value should be 12345 (min bid is 12345) + resp := new(types.GetHeaderResponse) + err := json.Unmarshal(rr.Body.Bytes(), resp) + require.NoError(t, err) + require.Equal(t, types.IntToU256(12345), resp.Data.Message.Value) + }) + t.Run("Invalid relay public key", func(t *testing.T) { backend := newTestBackend(t, 1, time.Second)