From 844cadf72f2ad9fef63fed557e1f8c14ab48c92f Mon Sep 17 00:00:00 2001 From: George Zhang Date: Fri, 29 Aug 2025 21:08:20 -0700 Subject: [PATCH 1/3] Add header-based configuration override for QuickNode integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements X-Flashbots-Origin-ID header support to enable guaranteed refund collection for enterprise partners like QuickNode. Changes: - Add presets field to CustomersConfig for header-based enforcement - Implement getEffectiveParameters() method with header override logic - Add graceful degradation for invalid preset configurations - Include unit tests for preset parsing and header override behavior When X-Flashbots-Origin-ID header is present, preset configuration takes precedence over URL parameters, ensuring partners receive guaranteed refund percentages. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- server/configuration_watcher.go | 64 ++++++++++++++----- server/configuration_watcher_test.go | 54 ++++++++++++++++ server/request_handler.go | 17 ++++- server/request_handler_test.go | 92 ++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 server/configuration_watcher_test.go create mode 100644 server/request_handler_test.go diff --git a/server/configuration_watcher.go b/server/configuration_watcher.go index 1ba9976..6b08b9a 100644 --- a/server/configuration_watcher.go +++ b/server/configuration_watcher.go @@ -2,17 +2,20 @@ package server import ( "errors" + "fmt" "maps" "net/url" "os" + "github.com/ethereum/go-ethereum/log" "gopkg.in/yaml.v3" ) var ErrCustomerNotConfigured = errors.New("customer is not configured") type CustomersConfig struct { - URLs map[string][]string `yaml:"urls"` + URLs map[string][]string `yaml:"urls"` + Presets map[string]string `yaml:"presets,omitempty"` } // ConfigurationWatcher @@ -20,31 +23,64 @@ type CustomersConfig struct { type ConfigurationWatcher struct { // CustomersConfig represents config for each custom with allowed list of configuration parameters ParsedCustomersConfig map[string][]URLParameters + // ParsedPresets contains pre-parsed preset configurations for header-based override + ParsedPresets map[string]URLParameters +} + +// parseURLToParameters converts a raw URL string to URLParameters +func parseURLToParameters(rawURL string) (URLParameters, error) { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return URLParameters{}, fmt.Errorf("failed to parse URL: %w", err) + } + + params, err := ExtractParametersFromUrl(parsedURL, nil) + if err != nil { + return URLParameters{}, fmt.Errorf("failed to extract parameters: %w", err) + } + + return params, nil } func NewConfigurationWatcher(customersConfig CustomersConfig) (*ConfigurationWatcher, error) { parsedCustomersConfig := make(map[string][]URLParameters) - for k, v := range customersConfig.URLs { - var allowedConfigs []URLParameters - for _, rawUrl := range v { - parsedUrl, err := url.Parse(rawUrl) + for customerID, urls := range customersConfig.URLs { + allowedConfigs := make([]URLParameters, 0, len(urls)) + for _, rawURL := range urls { + urlParam, err := parseURLToParameters(rawURL) if err != nil { - return nil, err + return nil, fmt.Errorf("invalid URL for customer %s: %w", customerID, err) } - URLParam, err := ExtractParametersFromUrl(parsedUrl, nil) - if err != nil { - return nil, err - } - allowedConfigs = append(allowedConfigs, URLParam) + allowedConfigs = append(allowedConfigs, urlParam) + } + parsedCustomersConfig[customerID] = allowedConfigs + } + + // Parse presets for header-based override + parsedPresets := make(map[string]URLParameters) + for originID, presetURL := range customersConfig.Presets { + params, err := parseURLToParameters(presetURL) + if err != nil { + // Log error but continue - graceful degradation + log.Error("Failed to parse preset configuration", "originID", originID, "url", presetURL, "error", err) + continue } - parsedCustomersConfig[k] = allowedConfigs + parsedPresets[originID] = params + log.Info("Loaded preset configuration", "originID", originID) } - return &ConfigurationWatcher{ParsedCustomersConfig: parsedCustomersConfig}, nil + + return &ConfigurationWatcher{ + ParsedCustomersConfig: parsedCustomersConfig, + ParsedPresets: parsedPresets, + }, nil } func ReadCustomerConfigFromFile(fileName string) (*ConfigurationWatcher, error) { if fileName == "" { - return &ConfigurationWatcher{ParsedCustomersConfig: make(map[string][]URLParameters)}, nil + return &ConfigurationWatcher{ + ParsedCustomersConfig: make(map[string][]URLParameters), + ParsedPresets: make(map[string]URLParameters), + }, nil } data, err := os.ReadFile(fileName) if err != nil { diff --git a/server/configuration_watcher_test.go b/server/configuration_watcher_test.go new file mode 100644 index 0000000..4d242ce --- /dev/null +++ b/server/configuration_watcher_test.go @@ -0,0 +1,54 @@ +package server + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConfigurationWatcherPresets(t *testing.T) { + // Test the core business logic: valid presets are parsed and available + config := CustomersConfig{ + URLs: map[string][]string{ + "quicknode": {"/fast?originId=quicknode"}, + }, + Presets: map[string]string{ + "quicknode": "/fast?originId=quicknode&refund=0x1234567890123456789012345678901234567890:90", + }, + } + + watcher, err := NewConfigurationWatcher(config) + require.NoError(t, err) + require.NotNil(t, watcher) + + // Core functionality: preset should be parsed and available + preset, exists := watcher.ParsedPresets["quicknode"] + require.True(t, exists) + require.Equal(t, "quicknode", preset.originId) + require.True(t, preset.fast) + require.Equal(t, 1, len(preset.pref.Validity.Refund)) +} + +func TestConfigurationWatcherInvalidPresets(t *testing.T) { + // Test graceful degradation: invalid presets are skipped, don't break startup + config := CustomersConfig{ + URLs: map[string][]string{ + "test": {"/fast?originId=test"}, + }, + Presets: map[string]string{ + "valid": "/fast?originId=valid", + "invalid": "://invalid-url", // This should be skipped + }, + } + + watcher, err := NewConfigurationWatcher(config) + require.NoError(t, err) // Should not fail startup + + // Valid preset loaded + _, exists := watcher.ParsedPresets["valid"] + require.True(t, exists) + + // Invalid preset skipped + _, exists = watcher.ParsedPresets["invalid"] + require.False(t, exists) +} \ No newline at end of file diff --git a/server/request_handler.go b/server/request_handler.go index afc12c4..ad1723a 100644 --- a/server/request_handler.go +++ b/server/request_handler.go @@ -73,6 +73,21 @@ func NewRpcRequestHandler( } } +// getEffectiveParameters determines the URL parameters to use for this request. +// It checks for header-based preset override first, then falls back to URL parsing. +func (r *RpcRequestHandler) getEffectiveParameters() (URLParameters, error) { + // Check for header-based preset override + if originID := r.req.Header.Get("X-Flashbots-Origin-ID"); originID != "" && r.configurationWatcher != nil { + if preset, exists := r.configurationWatcher.ParsedPresets[originID]; exists { + r.logger.Info("Using preset configuration", "originID", originID) + return preset, nil + } + r.logger.Info("Header present but no preset found", "originID", originID) + } + // Fall back to URL parsing + return ExtractParametersFromUrl(r.req.URL, r.builderNames) +} + // nolint func (r *RpcRequestHandler) process() { r.logger = r.logger.New("uid", r.uid) @@ -132,7 +147,7 @@ func (r *RpcRequestHandler) process() { } // mev-share parameters - urlParams, err := ExtractParametersFromUrl(r.req.URL, r.builderNames) + urlParams, err := r.getEffectiveParameters() if err != nil { r.logger.Warn("[process] Invalid auction preference", "error", err, "url", r.req.URL) res := AuctionPreferenceErrorToJSONRPCResponse(jsonReq, err) diff --git a/server/request_handler_test.go b/server/request_handler_test.go new file mode 100644 index 0000000..fec6089 --- /dev/null +++ b/server/request_handler_test.go @@ -0,0 +1,92 @@ +package server + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/log" + "github.com/stretchr/testify/require" +) + +func TestGetEffectiveParameters(t *testing.T) { + // Core business logic test: header with preset uses preset (ignores URL) + config := CustomersConfig{ + Presets: map[string]string{ + "quicknode": "/fast?originId=quicknode&refund=0x1234567890123456789012345678901234567890:90", + }, + } + + watcher, err := NewConfigurationWatcher(config) + require.NoError(t, err) + + // Request with header and different URL parameters + req := httptest.NewRequest(http.MethodPost, "/fast?originId=user-provided&refund=0xbad:10", nil) + req.Header.Set("X-Flashbots-Origin-ID", "quicknode") + + w := httptest.NewRecorder() + respw := http.ResponseWriter(w) + + handler := &RpcRequestHandler{ + respw: &respw, + req: req, + logger: log.New(), + builderNames: []string{"flashbots"}, + configurationWatcher: watcher, + } + + params, err := handler.getEffectiveParameters() + require.NoError(t, err) + + // Should use preset values, ignore URL + require.Equal(t, "quicknode", params.originId) + require.True(t, params.fast) + require.Equal(t, 1, len(params.pref.Validity.Refund)) // Preset refund, not URL refund +} + +func TestGetEffectiveParametersNoHeader(t *testing.T) { + // Fallback behavior: no header uses URL normally + req := httptest.NewRequest(http.MethodPost, "/fast?originId=normal-user", nil) + // No X-Flashbots-Origin-ID header + + w := httptest.NewRecorder() + respw := http.ResponseWriter(w) + + handler := &RpcRequestHandler{ + respw: &respw, + req: req, + logger: log.New(), + builderNames: []string{"flashbots"}, + configurationWatcher: &ConfigurationWatcher{ + ParsedPresets: make(map[string]URLParameters), + }, + } + + params, err := handler.getEffectiveParameters() + require.NoError(t, err) + require.Equal(t, "normal-user", params.originId) + require.True(t, params.fast) +} + +func TestGetEffectiveParametersHeaderNoPreset(t *testing.T) { + // Edge case: header present but no matching preset falls back to URL + req := httptest.NewRequest(http.MethodPost, "/fast?originId=fallback-user", nil) + req.Header.Set("X-Flashbots-Origin-ID", "unknown") + + w := httptest.NewRecorder() + respw := http.ResponseWriter(w) + + handler := &RpcRequestHandler{ + respw: &respw, + req: req, + logger: log.New(), + builderNames: []string{"flashbots"}, + configurationWatcher: &ConfigurationWatcher{ + ParsedPresets: make(map[string]URLParameters), + }, + } + + params, err := handler.getEffectiveParameters() + require.NoError(t, err) + require.Equal(t, "fallback-user", params.originId) +} \ No newline at end of file From 62557569e13eca89662cdf67a8f33c9f572562e1 Mon Sep 17 00:00:00 2001 From: TymKh Date: Wed, 3 Sep 2025 18:39:56 +0200 Subject: [PATCH 2/3] respect originId from url --- server/request_handler.go | 25 ++++++++++++++++--------- server/request_handler_test.go | 30 ++++++++++++++++-------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/server/request_handler.go b/server/request_handler.go index ad1723a..3ffbc8d 100644 --- a/server/request_handler.go +++ b/server/request_handler.go @@ -76,16 +76,23 @@ func NewRpcRequestHandler( // getEffectiveParameters determines the URL parameters to use for this request. // It checks for header-based preset override first, then falls back to URL parsing. func (r *RpcRequestHandler) getEffectiveParameters() (URLParameters, error) { - // Check for header-based preset override - if originID := r.req.Header.Get("X-Flashbots-Origin-ID"); originID != "" && r.configurationWatcher != nil { - if preset, exists := r.configurationWatcher.ParsedPresets[originID]; exists { - r.logger.Info("Using preset configuration", "originID", originID) - return preset, nil - } - r.logger.Info("Header present but no preset found", "originID", originID) + extracted, err := ExtractParametersFromUrl(r.req.URL, r.builderNames) + if err != nil { + return extracted, err + } + if r.configurationWatcher == nil { + return extracted, nil } - // Fall back to URL parsing - return ExtractParametersFromUrl(r.req.URL, r.builderNames) + originID := extracted.originId + if headerOriginID := r.req.Header.Get("X-Flashbots-Origin-ID"); headerOriginID != "" { + originID = headerOriginID + } + if preset, exists := r.configurationWatcher.ParsedPresets[originID]; exists { + r.logger.Info("Using preset configuration", "originID", originID) + return preset, nil + } + + return extracted, nil } // nolint diff --git a/server/request_handler_test.go b/server/request_handler_test.go index fec6089..468760f 100644 --- a/server/request_handler_test.go +++ b/server/request_handler_test.go @@ -5,6 +5,7 @@ import ( "net/http/httptest" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" ) @@ -16,17 +17,17 @@ func TestGetEffectiveParameters(t *testing.T) { "quicknode": "/fast?originId=quicknode&refund=0x1234567890123456789012345678901234567890:90", }, } - + watcher, err := NewConfigurationWatcher(config) require.NoError(t, err) - + // Request with header and different URL parameters - req := httptest.NewRequest(http.MethodPost, "/fast?originId=user-provided&refund=0xbad:10", nil) + req := httptest.NewRequest(http.MethodPost, "/fast?originId=user-provided&refund=0xdadB0d80178819F2319190D340ce9A924f783711:10", nil) req.Header.Set("X-Flashbots-Origin-ID", "quicknode") - + w := httptest.NewRecorder() respw := http.ResponseWriter(w) - + handler := &RpcRequestHandler{ respw: &respw, req: req, @@ -34,24 +35,25 @@ func TestGetEffectiveParameters(t *testing.T) { builderNames: []string{"flashbots"}, configurationWatcher: watcher, } - + params, err := handler.getEffectiveParameters() require.NoError(t, err) - + // Should use preset values, ignore URL require.Equal(t, "quicknode", params.originId) require.True(t, params.fast) require.Equal(t, 1, len(params.pref.Validity.Refund)) // Preset refund, not URL refund + require.Equal(t, params.pref.Validity.Refund[0].Address, common.HexToAddress("0x1234567890123456789012345678901234567890")) } func TestGetEffectiveParametersNoHeader(t *testing.T) { // Fallback behavior: no header uses URL normally req := httptest.NewRequest(http.MethodPost, "/fast?originId=normal-user", nil) // No X-Flashbots-Origin-ID header - + w := httptest.NewRecorder() respw := http.ResponseWriter(w) - + handler := &RpcRequestHandler{ respw: &respw, req: req, @@ -61,7 +63,7 @@ func TestGetEffectiveParametersNoHeader(t *testing.T) { ParsedPresets: make(map[string]URLParameters), }, } - + params, err := handler.getEffectiveParameters() require.NoError(t, err) require.Equal(t, "normal-user", params.originId) @@ -72,10 +74,10 @@ func TestGetEffectiveParametersHeaderNoPreset(t *testing.T) { // Edge case: header present but no matching preset falls back to URL req := httptest.NewRequest(http.MethodPost, "/fast?originId=fallback-user", nil) req.Header.Set("X-Flashbots-Origin-ID", "unknown") - + w := httptest.NewRecorder() respw := http.ResponseWriter(w) - + handler := &RpcRequestHandler{ respw: &respw, req: req, @@ -85,8 +87,8 @@ func TestGetEffectiveParametersHeaderNoPreset(t *testing.T) { ParsedPresets: make(map[string]URLParameters), }, } - + params, err := handler.getEffectiveParameters() require.NoError(t, err) require.Equal(t, "fallback-user", params.originId) -} \ No newline at end of file +} From 07a5f621cff732c27e25a9b93a8785d2a4e1bdb4 Mon Sep 17 00:00:00 2001 From: TymKh Date: Thu, 4 Sep 2025 18:12:28 +0200 Subject: [PATCH 3/3] rename origin-id to origin to ensure consistency with relay api --- server/request_handler.go | 2 +- server/request_handler_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/request_handler.go b/server/request_handler.go index 3ffbc8d..1ebd5ac 100644 --- a/server/request_handler.go +++ b/server/request_handler.go @@ -84,7 +84,7 @@ func (r *RpcRequestHandler) getEffectiveParameters() (URLParameters, error) { return extracted, nil } originID := extracted.originId - if headerOriginID := r.req.Header.Get("X-Flashbots-Origin-ID"); headerOriginID != "" { + if headerOriginID := r.req.Header.Get("X-Flashbots-Origin"); headerOriginID != "" { originID = headerOriginID } if preset, exists := r.configurationWatcher.ParsedPresets[originID]; exists { diff --git a/server/request_handler_test.go b/server/request_handler_test.go index 468760f..cfdf2e5 100644 --- a/server/request_handler_test.go +++ b/server/request_handler_test.go @@ -23,7 +23,7 @@ func TestGetEffectiveParameters(t *testing.T) { // Request with header and different URL parameters req := httptest.NewRequest(http.MethodPost, "/fast?originId=user-provided&refund=0xdadB0d80178819F2319190D340ce9A924f783711:10", nil) - req.Header.Set("X-Flashbots-Origin-ID", "quicknode") + req.Header.Set("X-Flashbots-Origin", "quicknode") w := httptest.NewRecorder() respw := http.ResponseWriter(w) @@ -73,7 +73,7 @@ func TestGetEffectiveParametersNoHeader(t *testing.T) { func TestGetEffectiveParametersHeaderNoPreset(t *testing.T) { // Edge case: header present but no matching preset falls back to URL req := httptest.NewRequest(http.MethodPost, "/fast?originId=fallback-user", nil) - req.Header.Set("X-Flashbots-Origin-ID", "unknown") + req.Header.Set("X-Flashbots-Origin", "unknown") w := httptest.NewRecorder() respw := http.ResponseWriter(w)