From 48892bfaf28252384d3906d00c10cc7f5634cbf0 Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Tue, 14 Oct 2025 01:48:53 +0530 Subject: [PATCH 1/4] fix: fast-track for RPC service --- tools/preconf-rpc/main.go | 9 ++ tools/preconf-rpc/sender/sender.go | 23 +++++ tools/preconf-rpc/sender/sender_test.go | 2 + tools/preconf-rpc/service/service.go | 113 ++++++++++++++++++++++++ 4 files changed, 147 insertions(+) diff --git a/tools/preconf-rpc/main.go b/tools/preconf-rpc/main.go index a7671dbdc..2bac4dd96 100644 --- a/tools/preconf-rpc/main.go +++ b/tools/preconf-rpc/main.go @@ -203,6 +203,13 @@ var ( EnvVars: []string{"PRECONF_RPC_WEBHOOK_URLS"}, } + optionAuthToken = &cli.StringFlag{ + Name: "auth-token", + Usage: "authentication token for securing endpoints", + EnvVars: []string{"PRECONF_RPC_AUTH_TOKEN"}, + Value: "", + } + optionLogFmt = &cli.StringFlag{ Name: "log-fmt", Usage: "log format to use, options are 'text' or 'json'", @@ -277,6 +284,7 @@ func main() { optionWebhookURLs, optionBidderThreshold, optionBidderTopup, + optionAuthToken, }, Action: func(c *cli.Context) error { logger, err := util.NewLogger( @@ -361,6 +369,7 @@ func main() { BridgeAddress: common.HexToAddress(c.String(optionBridgeAddress.Name)), PricerAPIKey: c.String(optionBlocknativeAPIKey.Name), Webhooks: c.StringSlice(optionWebhookURLs.Name), + Token: c.String(optionAuthToken.Name), } s, err := service.New(&config) diff --git a/tools/preconf-rpc/sender/sender.go b/tools/preconf-rpc/sender/sender.go index 6fda65c8e..3090b9740 100644 --- a/tools/preconf-rpc/sender/sender.go +++ b/tools/preconf-rpc/sender/sender.go @@ -134,6 +134,11 @@ type TxSender struct { processMu sync.RWMutex txnAttemptHistory *lru.Cache[common.Hash, *txnAttempt] notifier Notifier + fastTrack func(cmts []*bidderapiv1.Commitment, optedInSlot bool) bool +} + +func noOpFastTrack(_ []*bidderapiv1.Commitment, _ bool) bool { + return false } func NewTxSender( @@ -144,6 +149,7 @@ func NewTxSender( transferer Transferer, notifier Notifier, settlementChainId *big.Int, + fastTrack func(cmts []*bidderapiv1.Commitment, optedInSlot bool) bool, logger *slog.Logger, ) (*TxSender, error) { txnAttemptHistory, err := lru.New[common.Hash, *txnAttempt](1000) @@ -152,6 +158,10 @@ func NewTxSender( return nil, fmt.Errorf("failed to create transaction attempt history cache: %w", err) } + if fastTrack == nil { + fastTrack = noOpFastTrack + } + return &TxSender{ store: st, bidder: bidder, @@ -166,6 +176,7 @@ func NewTxSender( inflightAccount: make(map[common.Address]struct{}), txnAttemptHistory: txnAttemptHistory, notifier: notifier, + fastTrack: fastTrack, }, nil } @@ -431,6 +442,18 @@ BID_LOOP: continue } return err + case t.fastTrack(result.commitments, result.optedInSlot): + // If the commitments indicate that the transaction can be fast-tracked, + // we consider it pre-confirmed and skip further checks + txn.Status = TxStatusPreConfirmed + txn.BlockNumber = int64(result.blockNumber) + t.logger.Info( + "Transaction fast-tracked based on commitments", + "sender", txn.Sender.Hex(), + "type", txn.Type, + "blockNumber", result.blockNumber, + "bidAmount", result.bidAmount.String(), + ) case result.optedInSlot: if result.noOfProviders == len(result.commitments) { // This means that all builders have committed to the bid and it diff --git a/tools/preconf-rpc/sender/sender_test.go b/tools/preconf-rpc/sender/sender_test.go index 3bd8f90b2..e22263f8d 100644 --- a/tools/preconf-rpc/sender/sender_test.go +++ b/tools/preconf-rpc/sender/sender_test.go @@ -294,6 +294,7 @@ func TestSender(t *testing.T) { &mockTransferer{}, notifier, big.NewInt(1), // Settlement chain ID + nil, util.NewTestLogger(os.Stdout), ) if err != nil { @@ -562,6 +563,7 @@ func TestCancelTransaction(t *testing.T) { &mockTransferer{}, &mockNotifier{}, big.NewInt(1), // Settlement chain ID + nil, util.NewTestLogger(os.Stdout), ) if err != nil { diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index c127089fb..f50b4cf3b 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "database/sql" + "encoding/json" "errors" "fmt" "io" @@ -11,6 +12,7 @@ import ( "math/big" "net/http" "slices" + "strings" "time" "github.com/ethereum/go-ethereum/common" @@ -62,6 +64,7 @@ type Config struct { GasFeeCap *big.Int PricerAPIKey string Webhooks []string + Token string } type Service struct { @@ -234,6 +237,25 @@ func New(config *Config) (*Service, error) { blockTrackerDone := blockTracker.Start(ctx) healthChecker.Register(health.CloseChannelHealthCheck("BlockTracker", blockTrackerDone)) + allSlots := false + providers := []common.Address{} + fastTrackFn := func(cmts []*bidderapiv1.Commitment, optedInSlot bool) bool { + if !allSlots && !optedInSlot { + return false + } + if len(providers) == 0 { + return false + } + for _, p := range providers { + if !slices.ContainsFunc(cmts, func(cmt *bidderapiv1.Commitment) bool { + return common.HexToAddress(cmt.ProviderAddress).Cmp(p) == 0 + }) { + return false + } + } + return true + } + sndr, err := sender.NewTxSender( rpcstore, bidderClient, @@ -242,6 +264,7 @@ func New(config *Config) (*Service, error) { transferer, notifier, settlementChainID, + fastTrackFn, config.Logger.With("module", "txsender"), ) if err != nil { @@ -276,6 +299,96 @@ func New(config *Config) (*Service, error) { }) mux.Handle("/", rpcServer) + checkAuthorization := func(r *http.Request) error { + if config.Token == "" { + return errors.New("server not configured with authorization token") + } + + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + return errors.New("authorization header missing") + } + + // Expected format "Bearer " + headerToken, found := strings.CutPrefix(authHeader, "Bearer ") + if !found { + return errors.New("invalid authorization header format") + } + + if headerToken != config.Token { + return errors.New("unauthorized: invalid token") + } + + return nil + } + + mux.HandleFunc("POST /fast-track/enable", func(w http.ResponseWriter, r *http.Request) { + if err := checkAuthorization(r); err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + type fastTrackReq struct { + AllSlots bool + Providers []string + } + + var body fastTrackReq + + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest) + return + } + + allSlots = body.AllSlots + providers = make([]common.Address, 0, len(body.Providers)) + for _, p := range body.Providers { + if !common.IsHexAddress(p) { + http.Error(w, fmt.Sprintf("invalid provider address: %s", p), http.StatusBadRequest) + return + } + providers = append(providers, common.HexToAddress(p)) + } + + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("OK")) + }) + + mux.HandleFunc("POST /fast-track/disable", func(w http.ResponseWriter, r *http.Request) { + if err := checkAuthorization(r); err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + + allSlots = false + providers = []common.Address{} + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("OK")) + }) + + mux.HandleFunc("GET /fast-track/status", func(w http.ResponseWriter, r *http.Request) { + if err := checkAuthorization(r); err != nil { + http.Error(w, err.Error(), http.StatusUnauthorized) + return + } + type fastTrackStatus struct { + AllSlots bool + Providers []string + } + resp := fastTrackStatus{ + AllSlots: allSlots, + Providers: make([]string, 0, len(providers)), + } + for _, p := range providers { + resp.Providers = append(resp.Providers, p.Hex()) + } + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(&resp); err != nil { + http.Error(w, fmt.Sprintf("failed to encode response: %v", err), http.StatusInternalServerError) + return + } + }) + srv := http.Server{ Addr: fmt.Sprintf(":%d", config.HTTPPort), Handler: mux, From 53ef37439bf63ffc5f96c92f5f89299316d5fc12 Mon Sep 17 00:00:00 2001 From: Harsh pratap Singh Date: Tue, 14 Oct 2025 16:32:15 +0530 Subject: [PATCH 2/4] feat: add auth token to rpc --- .../templates/deployment.yaml | 13 ++++++--- .../templates/externalsecret.yaml | 29 +++++++++++++++++-- .../charts/mev-commit-preconf-rpc/values.yaml | 8 +++++ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/infrastructure/charts/mev-commit-preconf-rpc/templates/deployment.yaml b/infrastructure/charts/mev-commit-preconf-rpc/templates/deployment.yaml index bf3166900..b7e3ce7f9 100644 --- a/infrastructure/charts/mev-commit-preconf-rpc/templates/deployment.yaml +++ b/infrastructure/charts/mev-commit-preconf-rpc/templates/deployment.yaml @@ -35,9 +35,7 @@ spec: {{- end }} - --keystore-dir={{ .Values.keystore.dir }} - --keystore-password=$(KEYSTORE_PASSWORD) - {{- range .Values.config.l1RpcUrls }} - - --l1-rpc-urls={{ . }} - {{- end }} + - --l1-rpc-urls={{ .Values.config.l1RpcUrls | join "," }} - --settlement-rpc-url={{ .Values.config.settlementRpcUrl }} - --bidder-rpc-url={{ .Values.config.bidderRpcUrl }} - --l1-contract-addr={{ .Values.config.l1ContractAddr }} @@ -46,15 +44,22 @@ spec: - --bridge-address={{ .Values.config.bridgeAddress }} - --settlement-threshold={{ .Values.config.settlementThreshold }} - --settlement-topup={{ .Values.config.settlementTopup }} - - --auto-deposit-amount={{ .Values.config.autoDepositAmount }} + - --target-deposit-amount={{ .Values.config.targetDepositAmount }} + - --webhook-urls={{ .Values.config.webhookUrls | join "," }} - --gas-tip-cap={{ .Values.config.gasTipCap }} - --gas-fee-cap={{ .Values.config.gasFeeCap }} + - --auth-token=$(AUTH_TOKEN) env: - name: KEYSTORE_PASSWORD valueFrom: secretKeyRef: name: {{ include "preconf-rpc.fullname" . }}-keystore-password key: password + - name: AUTH_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "preconf-rpc.fullname" . }}-auth-token + key: token - name: POSTGRES_PASSWORD value: {{ .Values.postgresql.password | quote }} - name: PRECONF_RPC_PG_HOST diff --git a/infrastructure/charts/mev-commit-preconf-rpc/templates/externalsecret.yaml b/infrastructure/charts/mev-commit-preconf-rpc/templates/externalsecret.yaml index c263f1b46..b7fc69b83 100644 --- a/infrastructure/charts/mev-commit-preconf-rpc/templates/externalsecret.yaml +++ b/infrastructure/charts/mev-commit-preconf-rpc/templates/externalsecret.yaml @@ -1,6 +1,6 @@ --- # ExternalSecret for AWS SM - Keystore + Filename -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: {{ include "preconf-rpc.fullname" . }}-keystore @@ -29,7 +29,7 @@ spec: --- # ExternalSecret for keystore password -apiVersion: external-secrets.io/v1beta1 +apiVersion: external-secrets.io/v1 kind: ExternalSecret metadata: name: {{ include "preconf-rpc.fullname" . }}-keystore-password @@ -51,3 +51,28 @@ spec: remoteRef: key: {{ .Values.keystore.awsSecretName }} property: {{ .Values.keystore.properties.keystorePassword }} + +--- +# ExternalSecret for auth token +apiVersion: external-secrets.io/v1 +kind: ExternalSecret +metadata: + name: {{ include "preconf-rpc.fullname" . }}-auth-token + labels: + {{- include "preconf-rpc.labels" . | nindent 4 }} + annotations: + helm.sh/hook: pre-install,pre-upgrade + helm.sh/hook-weight: "-2" +spec: + refreshInterval: {{ .Values.authToken.refreshInterval }} + secretStoreRef: + name: {{ .Values.authToken.secretStore.name }} + kind: {{ .Values.authToken.secretStore.kind }} + target: + name: {{ include "preconf-rpc.fullname" . }}-auth-token + creationPolicy: Owner + data: + - secretKey: token + remoteRef: + key: {{ .Values.authToken.awsSecretName }} + property: {{ .Values.authToken.property }} diff --git a/infrastructure/charts/mev-commit-preconf-rpc/values.yaml b/infrastructure/charts/mev-commit-preconf-rpc/values.yaml index 8c8bc8f1f..0f6e81565 100644 --- a/infrastructure/charts/mev-commit-preconf-rpc/values.yaml +++ b/infrastructure/charts/mev-commit-preconf-rpc/values.yaml @@ -92,6 +92,14 @@ keystore: keystoreFilename: "" keystorePassword: "" +authToken: + refreshInterval: 1h + secretStore: + kind: ClusterSecretStore + name: aws-cluster-secret-store + awsSecretName: "" + property: "" + # PostgreSQL configuration postgresql: host: "preconf-rpc-pg.default.svc.cluster.local" From 76145767f29003d7fca1239d425133b1f7a3739a Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Tue, 14 Oct 2025 02:16:03 +0530 Subject: [PATCH 3/4] fix: fast-track for RPC service --- tools/preconf-rpc/sender/sender.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/preconf-rpc/sender/sender.go b/tools/preconf-rpc/sender/sender.go index 3090b9740..0fa5ced37 100644 --- a/tools/preconf-rpc/sender/sender.go +++ b/tools/preconf-rpc/sender/sender.go @@ -454,6 +454,8 @@ BID_LOOP: "blockNumber", result.blockNumber, "bidAmount", result.bidAmount.String(), ) + t.clearBlockAttemptHistory(txn) + break BID_LOOP case result.optedInSlot: if result.noOfProviders == len(result.commitments) { // This means that all builders have committed to the bid and it From ea8e445f94ab107c5d777f172c3169e8ff9c2e4e Mon Sep 17 00:00:00 2001 From: Alok Nerurkar Date: Tue, 14 Oct 2025 18:46:32 +0530 Subject: [PATCH 4/4] fix: fast-track for RPC service --- tools/preconf-rpc/service/service.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tools/preconf-rpc/service/service.go b/tools/preconf-rpc/service/service.go index f50b4cf3b..6642dd2b9 100644 --- a/tools/preconf-rpc/service/service.go +++ b/tools/preconf-rpc/service/service.go @@ -38,6 +38,8 @@ import ( "google.golang.org/grpc/credentials" ) +const defaultMaxBodySize = 1 * 1024 * 1024 // 1 MB + type Config struct { Logger *slog.Logger PgHost string @@ -333,20 +335,30 @@ func New(config *Config) (*Service, error) { Providers []string } - var body fastTrackReq + var req fastTrackReq + + r.Body = http.MaxBytesReader(w, r.Body, defaultMaxBodySize) + defer func() { + _ = r.Body.Close() + }() - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, fmt.Sprintf("failed to decode body: %v", err), http.StatusBadRequest) return } - allSlots = body.AllSlots - providers = make([]common.Address, 0, len(body.Providers)) - for _, p := range body.Providers { + allSlots = req.AllSlots + providers = make([]common.Address, 0, len(req.Providers)) + for _, p := range req.Providers { if !common.IsHexAddress(p) { http.Error(w, fmt.Sprintf("invalid provider address: %s", p), http.StatusBadRequest) return } + if slices.ContainsFunc(providers, func(addr common.Address) bool { + return addr.Cmp(common.HexToAddress(p)) == 0 + }) { + continue + } providers = append(providers, common.HexToAddress(p)) }