From b81c35c7468edfcf843045560f64b23a82d3c08c Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Thu, 24 Apr 2025 13:35:31 +0200 Subject: [PATCH 1/4] Persistent TLS certificates --- cmd/receiver-proxy/main.go | 27 +++++++++++++++++++++++++-- go.mod | 2 +- go.sum | 4 ++-- proxy/receiver_proxy.go | 5 ++++- proxy/receiver_proxy_test.go | 17 ++++++++++------- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/cmd/receiver-proxy/main.go b/cmd/receiver-proxy/main.go index 1acff0a..c81fd2d 100644 --- a/cmd/receiver-proxy/main.go +++ b/cmd/receiver-proxy/main.go @@ -23,6 +23,9 @@ const ( flagSystemListenAddr = "system-listen-addr" flagCertListenAddr = "cert-listen-addr" flagMaxUserRPS = "max-user-requests-per-second" + + flagCertPath = "cert-path" + flagCertKeyPath = "cert-key-path" ) var flags = []cli.Flag{ @@ -111,6 +114,16 @@ var flags = []cli.Flag{ Usage: "generated certificate hosts", EnvVars: []string{"CERT_HOSTS"}, }, + &cli.DurationFlag{ + Name: flagCertPath, + Usage: "path where to store the generated certificate", + EnvVars: []string{"CERT_PATH"}, + }, + &cli.StringFlag{ + Name: flagCertKeyPath, + Usage: "path where to store the generated certificate key", + EnvVars: []string{"CERT_KEY_PATH"}, + }, // Logging, metrics and debug &cli.StringFlag{ @@ -216,8 +229,7 @@ func runMain(cCtx *cli.Context) error { builderEndpoint := cCtx.String("builder-endpoint") rpcEndpoint := cCtx.String("rpc-endpoint") - certDuration := cCtx.Duration("cert-duration") - certHosts := cCtx.StringSlice("cert-hosts") + builderConfigHubEndpoint := cCtx.String("builder-confighub-endpoint") archiveEndpoint := cCtx.String("orderflow-archive-endpoint") flashbotsSignerStr := cCtx.String("flashbots-orderflow-signer-address") @@ -227,10 +239,21 @@ func runMain(cCtx *cli.Context) error { maxUserRPS := cCtx.Int(flagMaxUserRPS) + certDuration := cCtx.Duration("cert-duration") + certHosts := cCtx.StringSlice("cert-hosts") + certPath := cCtx.String(flagCertPath) + certKeyPath := cCtx.String(flagCertKeyPath) + if certPath == "" || certKeyPath == "" { + log.Error("cert-path and cert-key-path must be set") + return nil + } + proxyConfig := &proxy.ReceiverProxyConfig{ ReceiverProxyConstantConfig: proxy.ReceiverProxyConstantConfig{Log: log, FlashbotsSignerAddress: flashbotsSignerAddress}, CertValidDuration: certDuration, CertHosts: certHosts, + CertPath: certPath, + CertKeyPath: certKeyPath, BuilderConfigHubEndpoint: builderConfigHubEndpoint, ArchiveEndpoint: archiveEndpoint, ArchiveConnections: connectionsPerPeer, diff --git a/go.mod b/go.mod index a40fa87..5298cc6 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/VictoriaMetrics/metrics v1.35.1 github.com/cenkalti/backoff v2.2.1+incompatible github.com/ethereum/go-ethereum v1.15.5 - github.com/flashbots/go-utils v0.10.1-0.20250416132112-39233b64a8c5 + github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index f4b8622..c4a8ee3 100644 --- a/go.sum +++ b/go.sum @@ -32,10 +32,10 @@ github.com/ethereum/go-ethereum v1.15.5 h1:Fo2TbBWC61lWVkFw9tsMoHCNX1ndpuaQBRJ8H github.com/ethereum/go-ethereum v1.15.5/go.mod h1:1LG2LnMOx2yPRHR/S+xuipXH29vPr6BIH6GElD8N/fo= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -github.com/flashbots/go-utils v0.10.0 h1:75XWewRO5GIhdLn8+vqdzzuoqJh+j8wN54A++Id7W0Y= -github.com/flashbots/go-utils v0.10.0/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= github.com/flashbots/go-utils v0.10.1-0.20250416132112-39233b64a8c5 h1:oEfjmk2NQ/FtX+1h1Rx4PMpJtbNAa6T/l6otbUmzZp8= github.com/flashbots/go-utils v0.10.1-0.20250416132112-39233b64a8c5/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= +github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5 h1:F6pO8NC+JSG8jSFzTOAjP4TaR+CwZKygQ4vSLT+XPPw= +github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= diff --git a/proxy/receiver_proxy.go b/proxy/receiver_proxy.go index 91c1bed..b15150a 100644 --- a/proxy/receiver_proxy.go +++ b/proxy/receiver_proxy.go @@ -76,8 +76,11 @@ type ReceiverProxyConstantConfig struct { type ReceiverProxyConfig struct { ReceiverProxyConstantConfig + CertValidDuration time.Duration CertHosts []string + CertPath string + CertKeyPath string BuilderConfigHubEndpoint string ArchiveEndpoint string @@ -99,7 +102,7 @@ func NewReceiverProxy(config ReceiverProxyConfig) (*ReceiverProxy, error) { return nil, err } - cert, key, err := utils_tls.GenerateTLS(config.CertValidDuration, config.CertHosts) + cert, key, err := utils_tls.GetOrGenerateTLS(config.CertPath, config.CertKeyPath, config.CertValidDuration, config.CertHosts) if err != nil { return nil, err } diff --git a/proxy/receiver_proxy_test.go b/proxy/receiver_proxy_test.go index 1f40571..8ff248e 100644 --- a/proxy/receiver_proxy_test.go +++ b/proxy/receiver_proxy_test.go @@ -74,11 +74,11 @@ func (setup *OrderflowProxyTestSetup) Close() { setup.proxy.Stop() } -func StartTestOrderflowProxy(name string) (*OrderflowProxyTestSetup, error) { +func StartTestOrderflowProxy(name, certPath, certKeyPath string) (*OrderflowProxyTestSetup, error) { localBuilderRequests := make(chan *RequestData, 1) localBuilderServer := ServeHTTPRequestToChan(localBuilderRequests) - proxy := createProxy(localBuilderServer.URL, name) + proxy := createProxy(localBuilderServer.URL, name, certPath, certKeyPath) publicProxyServer := &http.Server{ //nolint:gosec Handler: proxy.SystemHandler, TLSConfig: proxy.TLSConfig(), @@ -169,7 +169,7 @@ func TestMain(m *testing.M) { defer archiveServer.Close() for i := range 3 { - proxy, err := StartTestOrderflowProxy(fmt.Sprintf("proxy:%d", i)) + proxy, err := StartTestOrderflowProxy(fmt.Sprintf("proxy:%d", i), "/tmp/orderflow-proxy-test.cert", "/tmp/orderflow-proxy-test.key") proxies = append(proxies, proxy) if err != nil { panic(err) @@ -184,7 +184,7 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func createProxy(localBuilder, name string) *ReceiverProxy { +func createProxy(localBuilder, name, certPath, certKeyPath string) *ReceiverProxy { log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})) proxy, err := NewReceiverProxy(ReceiverProxyConfig{ ReceiverProxyConstantConfig: ReceiverProxyConstantConfig{ @@ -192,8 +192,11 @@ func createProxy(localBuilder, name string) *ReceiverProxy { Name: name, FlashbotsSignerAddress: flashbotsSigner.Address(), }, - CertValidDuration: time.Hour * 24, - CertHosts: []string{"localhost", "127.0.0.1"}, + CertValidDuration: time.Hour * 24, + CertHosts: []string{"localhost", "127.0.0.1"}, + CertPath: certPath, + CertKeyPath: certKeyPath, + BuilderConfigHubEndpoint: builderHub.URL, ArchiveEndpoint: archiveServer.URL, LocalBuilderEndpoint: localBuilder, @@ -551,7 +554,7 @@ func TestProxyBidSubsidiseBlockCall(t *testing.T) { } func TestBuilderNetRootCall(t *testing.T) { - proxy, err := StartTestOrderflowProxy("1") + proxy, err := StartTestOrderflowProxy("1", "/tmp/orderflow-proxy-test.cert", "/tmp/orderflow-proxy-test.key") require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "/", nil) From 3afa50de4b3730867fe58bff6d3d4600c14b9773 Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Thu, 24 Apr 2025 13:41:31 +0200 Subject: [PATCH 2/4] docs in html --- cmd/receiver-proxy/main.go | 4 ++-- proxy/html/index.html | 39 ++++++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/cmd/receiver-proxy/main.go b/cmd/receiver-proxy/main.go index c81fd2d..f417348 100644 --- a/cmd/receiver-proxy/main.go +++ b/cmd/receiver-proxy/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "log" "net/http" "net/http/pprof" @@ -244,8 +245,7 @@ func runMain(cCtx *cli.Context) error { certPath := cCtx.String(flagCertPath) certKeyPath := cCtx.String(flagCertKeyPath) if certPath == "" || certKeyPath == "" { - log.Error("cert-path and cert-key-path must be set") - return nil + return errors.New("cert-path and cert-key-path must be set") } proxyConfig := &proxy.ReceiverProxyConfig{ diff --git a/proxy/html/index.html b/proxy/html/index.html index 7f18cd6..7f00424 100644 --- a/proxy/html/index.html +++ b/proxy/html/index.html @@ -120,24 +120,36 @@

Sending Orderflow to BuilderNet

Example curl Request

curl https://_BUILDERNET_INSTANCE_ \
     --cacert builder-cert.pem \ # or using --insecure
+    --header 'Content-Type: application/json' \
     --header 'X-Flashbots-Signature: _public_key_address_:_signature_' \
     --data '{
-        "jsonrpc":"2.0",
-        "method":"eth_sendRawTransaction",
-        "params":["0x000000..."],
-        "id":1
-    }'
-

See also the full documentation at buildernet.org/docs/send-orderflow

- -
- Note: Currently, requests are rate-limited to 3 requests / IP / second. This is expected to be raised soon. -
+ "id": 1, + "jsonrpc": "2.0", + "method": "eth_sendBundle", + "params": [{ + "txs": [ + "0x100000...", "0x200000..." + ], + "blockNumber": "0x1361bd3" + }] + }' + +

See also the full documentation at +

+

Downloading the TLS certificate

You can get the TLS certificate through a TEE-attested channel (see "TEE Proof Validation" below), or download it directly with curl:

curl -w %{certs} -k https://_BUILDERNET_INSTANCE_
+
+ Note: Currently, requests are rate-limited to 3 requests / IP / second. This is expected to be raised soon. +
+
@@ -145,15 +157,10 @@

Downloading the TLS certificate

Instance TLS Certificate

-

BuilderNet instances create (and rotate) their own unique TLS certificate, and use it to prove their identity and to encrypt incoming traffic.

+

BuilderNet instances create their own unique TLS certificate, and use it to prove their identity and to encrypt incoming traffic.

This is the TLS certificate of this specific instance:

{{ .Cert }}
- -
- Note: The certificate changes on every restart of the server. This is a feature, not a bug, as the certificate - represents the unique identity of this server, and the private key never leaves the in-process memory. -

From 1c54971beca63ddab0cdb4c3d5756a77f33683ab Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Fri, 25 Apr 2025 10:25:09 +0200 Subject: [PATCH 3/4] tests: use temp dirs --- proxy/receiver_proxy_test.go | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/proxy/receiver_proxy_test.go b/proxy/receiver_proxy_test.go index 8ff248e..b827350 100644 --- a/proxy/receiver_proxy_test.go +++ b/proxy/receiver_proxy_test.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptest" "os" + "path" "testing" "time" @@ -117,6 +118,12 @@ func StartTestOrderflowProxy(name, certPath, certKeyPath string) (*OrderflowProx }, nil } +func check(err error) { + if err != nil { + panic(err) + } +} + func TestMain(m *testing.M) { signer, err := signature.NewRandomSigner() if err != nil { @@ -168,17 +175,27 @@ func TestMain(m *testing.M) { archiveServer = ServeHTTPRequestToChan(archiveServerRequests) defer archiveServer.Close() + tempDirs := make([]string, 0) for i := range 3 { - proxy, err := StartTestOrderflowProxy(fmt.Sprintf("proxy:%d", i), "/tmp/orderflow-proxy-test.cert", "/tmp/orderflow-proxy-test.key") + tempDir, err := os.MkdirTemp("", "orderflow-proxy-test") + check(err) + tempDirs = append(tempDirs, tempDir) + certPath := path.Join(tempDir, "cert") + keyPath := path.Join(tempDir, "key") + + proxy, err := StartTestOrderflowProxy(fmt.Sprintf("proxy:%d", i), certPath, keyPath) proxies = append(proxies, proxy) - if err != nil { - panic(err) - } + check(err) } + defer func() { for _, prx := range proxies { prx.Close() } + + for _, dir := range tempDirs { + _ = os.RemoveAll(dir) + } }() os.Exit(m.Run()) @@ -554,7 +571,10 @@ func TestProxyBidSubsidiseBlockCall(t *testing.T) { } func TestBuilderNetRootCall(t *testing.T) { - proxy, err := StartTestOrderflowProxy("1", "/tmp/orderflow-proxy-test.cert", "/tmp/orderflow-proxy-test.key") + tempDir := t.TempDir() + certPath := path.Join(tempDir, "cert") + keyPath := path.Join(tempDir, "key") + proxy, err := StartTestOrderflowProxy("1", certPath, keyPath) require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "/", nil) From 927d7a09646328e1a47de1dbc2aa53a3004df95e Mon Sep 17 00:00:00 2001 From: Chris Hager Date: Fri, 25 Apr 2025 10:28:44 +0200 Subject: [PATCH 4/4] update go-utils to v0.11.0 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5298cc6..f7845da 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/VictoriaMetrics/metrics v1.35.1 github.com/cenkalti/backoff v2.2.1+incompatible github.com/ethereum/go-ethereum v1.15.5 - github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5 + github.com/flashbots/go-utils v0.11.0 github.com/google/uuid v1.6.0 github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index c4a8ee3..f8c4725 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/flashbots/go-utils v0.10.1-0.20250416132112-39233b64a8c5 h1:oEfjmk2NQ github.com/flashbots/go-utils v0.10.1-0.20250416132112-39233b64a8c5/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5 h1:F6pO8NC+JSG8jSFzTOAjP4TaR+CwZKygQ4vSLT+XPPw= github.com/flashbots/go-utils v0.10.1-0.20250424105414-d6d0a6a11ce5/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= +github.com/flashbots/go-utils v0.11.0 h1:MuI9OOl40MukSL2ucKKQG1sxxl5Cqjla41TRubGNu0w= +github.com/flashbots/go-utils v0.11.0/go.mod h1:i4xxEB6sHDFfNWEIfh+rP6nx3LxynEn8AOZa05EYgwA= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=