From be998f769d5fd354d4eba9190f302ef771ab9990 Mon Sep 17 00:00:00 2001 From: aloknerurkar Date: Sat, 13 Jul 2024 00:19:03 +0530 Subject: [PATCH] feat: add new API for provider registration (#229) --- .../templates/jobs/mev-commit-oracle.nomad.j2 | 1 + .../nomad/playbooks/variables/profiles.yml | 2 + oracle/cmd/main.go | 9 ++ oracle/pkg/apiserver/apiserver.go | 100 ++++++++++++++++++ oracle/pkg/node/node.go | 24 +++++ p2p/pkg/autodepositor/autodepositor.go | 6 +- p2p/pkg/node/node.go | 5 +- p2p/pkg/rpc/provider/service_test.go | 4 +- 8 files changed, 146 insertions(+), 5 deletions(-) diff --git a/infrastructure/nomad/playbooks/templates/jobs/mev-commit-oracle.nomad.j2 b/infrastructure/nomad/playbooks/templates/jobs/mev-commit-oracle.nomad.j2 index 45551684b..2f232f817 100644 --- a/infrastructure/nomad/playbooks/templates/jobs/mev-commit-oracle.nomad.j2 +++ b/infrastructure/nomad/playbooks/templates/jobs/mev-commit-oracle.nomad.j2 @@ -173,6 +173,7 @@ job "{{ job.name }}" { MEV_ORACLE_KEYSTORE_PATH="/local/data-{{ env "NOMAD_ALLOC_INDEX" }}/keystore" MEV_ORACLE_KEYSTORE_FILENAME="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.{% endraw %}{{ job.artifacts | selectattr('keystore', 'defined') | map(attribute='keystore.name') | first }}{% raw %}_filename }}{{ end }}" MEV_ORACLE_KEYSTORE_PASSWORD="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.{% endraw %}{{ job.artifacts | selectattr('keystore', 'defined') | map(attribute='keystore.name') | first }}{% raw %}_password }}{{ end }}" + MEV_ORACLE_REGISTER_PROVIDER_API_AUTH_TOKEN="{{ with secret "secret/data/mev-commit" }}{{ .Data.data.oracle_register_provider_api_auth_token }}{{ end }}" {{- range nomadService "mev-commit-oracle" }} {{- if contains "http" .Tags }} MEV_ORACLE_HTTP_PORT="{{ .Port }}" diff --git a/infrastructure/nomad/playbooks/variables/profiles.yml b/infrastructure/nomad/playbooks/variables/profiles.yml index ece0ee884..bec42ecaf 100644 --- a/infrastructure/nomad/playbooks/variables/profiles.yml +++ b/infrastructure/nomad/playbooks/variables/profiles.yml @@ -452,6 +452,8 @@ jobs: template: mev-commit-oracle.nomad.j2 artifacts: - *oracle_artifact + - auth_token: + name: oracle_register_provider_api_auth_token - keystore: name: oracle_keystore allocation: true diff --git a/oracle/cmd/main.go b/oracle/cmd/main.go index e0dd72f66..cf6ac1cc3 100644 --- a/oracle/cmd/main.go +++ b/oracle/cmd/main.go @@ -210,6 +210,13 @@ var ( EnvVars: []string{"MEV_ORACLE_KEYSTORE_PATH"}, Value: filepath.Join(defaultConfigDir, defaultKeystore), }) + + optionRegistrationAuthToken = altsrc.NewStringFlag(&cli.StringFlag{ + Name: "register-provider-auth-token", + Usage: "Authorization token for provider registration", + EnvVars: []string{"MEV_ORACLE_REGISTER_PROVIDER_API_AUTH_TOKEN"}, + Required: true, + }) ) func main() { @@ -237,6 +244,7 @@ func main() { optionOverrideWinners, optionKeystorePath, optionKeystorePassword, + optionRegistrationAuthToken, } app := &cli.App{ Name: "mev-oracle", @@ -322,6 +330,7 @@ func launchOracleWithConfig(c *cli.Context) error { PgDbname: c.String(optionPgDbname.Name), LaggerdMode: c.Int(optionLaggerdMode.Name), OverrideWinners: c.StringSlice(optionOverrideWinners.Name), + RegistrationAuthToken: c.String(optionRegistrationAuthToken.Name), }) if err != nil { return fmt.Errorf("failed starting node: %w", err) diff --git a/oracle/pkg/apiserver/apiserver.go b/oracle/pkg/apiserver/apiserver.go index bd5c1a5f2..a043bc9ad 100644 --- a/oracle/pkg/apiserver/apiserver.go +++ b/oracle/pkg/apiserver/apiserver.go @@ -8,12 +8,18 @@ import ( "net" "net/http" "net/http/pprof" + "strings" "sync" "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" lru "github.com/hashicorp/golang-lru/v2" + blocktracker "github.com/primev/mev-commit/contracts-abi/clients/BlockTracker" + providerregistry "github.com/primev/mev-commit/contracts-abi/clients/ProviderRegistry" "github.com/primev/mev-commit/oracle/pkg/updater" "github.com/primev/mev-commit/x/contracts/events" + "github.com/primev/mev-commit/x/contracts/txmonitor" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -40,6 +46,9 @@ type Service struct { blockStats *lru.Cache[uint64, *BlockStats] providerStakes *lru.Cache[string, *ProviderBalances] bidderAllowances *lru.Cache[uint64, []*BidderAllowance] + blockTracker *blocktracker.BlocktrackerTransactorSession + providerRegistry *providerregistry.ProviderregistryCallerSession + monitor *txmonitor.Monitor lastBlock uint64 shutdown chan struct{} } @@ -49,6 +58,10 @@ func New( logger *slog.Logger, evm events.EventManager, store Store, + token string, + blockTracker *blocktracker.BlocktrackerTransactorSession, + providerRegistry *providerregistry.ProviderregistryCallerSession, + monitor *txmonitor.Monitor, ) *Service { blockStats, _ := lru.New[uint64, *BlockStats](10000) providerStakes, _ := lru.New[string, *ProviderBalances](1000) @@ -60,6 +73,9 @@ func New( metricsRegistry: newMetrics(), evtMgr: evm, store: store, + blockTracker: blockTracker, + providerRegistry: providerRegistry, + monitor: monitor, shutdown: make(chan struct{}), blockStats: blockStats, providerStakes: providerStakes, @@ -71,10 +87,94 @@ func New( logger.Error("failed to configure dashboard", "error", err) } + srv.router.Handle("/register_provider", srv.registerProvider(token)) + srv.registerDebugEndpoints() return srv } +func (s *Service) registerProvider(token string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + http.Error(w, "Authorization header missing", http.StatusUnauthorized) + return + } + + // Expected format "Bearer " + headerToken, found := strings.CutPrefix(authHeader, "Bearer ") + if !found { + http.Error(w, "Invalid Authorization header format", http.StatusUnauthorized) + return + } + + if headerToken != token { + http.Error(w, "Unauthorized", http.StatusUnauthorized) + return + } + + provider := r.URL.Query().Get("provider") + if provider == "" { + http.Error(w, "Provider not specified", http.StatusBadRequest) + return + } + + if !common.IsHexAddress(provider) { + http.Error(w, "Invalid provider address", http.StatusBadRequest) + return + } + + providerAddress := common.HexToAddress(provider) + + grafiti := r.URL.Query().Get("grafiti") + if grafiti == "" { + http.Error(w, "Grafiti not specified", http.StatusBadRequest) + return + } + + minStake, err := s.providerRegistry.MinStake() + if err != nil { + http.Error(w, "Failed to get minimum stake amount", http.StatusInternalServerError) + return + } + + stake, err := s.providerRegistry.CheckStake(providerAddress) + if err != nil { + http.Error(w, "Failed to check provider stake", http.StatusInternalServerError) + return + } + + if stake.Cmp(minStake) < 0 { + http.Error(w, "Insufficient stake", http.StatusBadRequest) + return + } + + txn, err := s.blockTracker.AddBuilderAddress(grafiti, providerAddress) + if err != nil { + http.Error(w, "Failed to add provider mapping", http.StatusInternalServerError) + return + } + + receipt, err := s.monitor.WaitForReceipt(context.Background(), txn) + if err != nil { + http.Error(w, "Failed to get receipt for transaction", http.StatusInternalServerError) + return + } + + if receipt.Status != types.ReceiptStatusSuccessful { + http.Error(w, "Transaction failed", http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + }) +} + func (s *Service) registerDebugEndpoints() { // register metrics handler s.router.Handle("/metrics", promhttp.HandlerFor(s.metricsRegistry, promhttp.HandlerOpts{})) diff --git a/oracle/pkg/node/node.go b/oracle/pkg/node/node.go index af7dd8003..43fcf48f8 100644 --- a/oracle/pkg/node/node.go +++ b/oracle/pkg/node/node.go @@ -57,6 +57,7 @@ type Options struct { PgDbname string LaggerdMode int OverrideWinners []string + RegistrationAuthToken string } type Node struct { @@ -248,10 +249,32 @@ func NewNode(opts *Options) (*Node, error) { updtrClosed := updtr.Start(ctx) + providerRegistry, err := providerregistry.NewProviderregistryCaller( + opts.ProviderRegistryContractAddr, + settlementClient, + ) + if err != nil { + nd.logger.Error("failed to instantiate provider registry contract", "error", err) + cancel() + return nil, err + } + + providerRegistryCaller := &providerregistry.ProviderregistryCallerSession{ + Contract: providerRegistry, + CallOpts: bind.CallOpts{ + From: opts.KeySigner.GetAddress(), + Pending: false, + }, + } + srv := apiserver.New( nd.logger.With("component", "apiserver"), evtMgr, st, + opts.RegistrationAuthToken, + blockTrackerTransactor, + providerRegistryCaller, + monitor, ) pubDone := eventsPublisher.Start(ctx, contractAddrs...) @@ -461,6 +484,7 @@ func (i *infiniteRetryL1Client) BlockByNumber(ctx context.Context, number *big.I } return blk, nil } + func setBuilderMapping( ctx context.Context, bt *blocktracker.BlocktrackerTransactorSession, diff --git a/p2p/pkg/autodepositor/autodepositor.go b/p2p/pkg/autodepositor/autodepositor.go index 2fcc4495c..66425d4dc 100644 --- a/p2p/pkg/autodepositor/autodepositor.go +++ b/p2p/pkg/autodepositor/autodepositor.go @@ -16,6 +16,8 @@ import ( "golang.org/x/sync/errgroup" ) +var ErrNotRunning = fmt.Errorf("auto deposit tracker is not running") + type OptsGetter func(context.Context) (*bind.TransactOpts, error) type BidderRegistryContract interface { @@ -228,7 +230,7 @@ func (adt *AutoDepositTracker) Stop() ([]*big.Int, error) { defer adt.startMu.Unlock() if !adt.isWorking { - return nil, fmt.Errorf("auto deposit tracker is not running") + return nil, ErrNotRunning } if adt.cancelFunc != nil { adt.cancelFunc() @@ -247,7 +249,7 @@ func (adt *AutoDepositTracker) Stop() ([]*big.Int, error) { adt.isWorking = false - adt.logger.Info("stop auto deposit tracker", "windows", windowNumbers) + adt.logger.Info("stop auto deposit tracker", "windowsToWithdraw", windowNumbers) return windowNumbers, nil } diff --git a/p2p/pkg/node/node.go b/p2p/pkg/node/node.go index 5cf4aa7d8..68d96ebb1 100644 --- a/p2p/pkg/node/node.go +++ b/p2p/pkg/node/node.go @@ -661,8 +661,11 @@ func (n *Node) Close() error { } _, adErr := n.autoDeposit.Stop() + if adErr != nil && !errors.Is(adErr, autodepositor.ErrNotRunning) { + return errors.Join(err, adErr) + } - return errors.Join(err, adErr) + return err } type noOpBidProcessor struct{} diff --git a/p2p/pkg/rpc/provider/service_test.go b/p2p/pkg/rpc/provider/service_test.go index 7c34eb783..abe6a0764 100644 --- a/p2p/pkg/rpc/provider/service_test.go +++ b/p2p/pkg/rpc/provider/service_test.go @@ -44,7 +44,7 @@ func (t *testRegistryContract) MinStake(_ *bind.CallOpts) (*big.Int, error) { func (t *testRegistryContract) ParseProviderRegistered(log types.Log) (*providerregistry.ProviderregistryProviderRegistered, error) { return &providerregistry.ProviderregistryProviderRegistered{ - Provider: common.Address{}, + Provider: common.Address{}, StakedAmount: t.stake, }, nil } @@ -97,7 +97,7 @@ func startServer(t *testing.T) (providerapiv1.ProviderClient, *providerapi.Servi if err := baseServer.Serve(lis); err != nil { // Ignore "use of closed network connection" error if opErr, ok := err.(*net.OpError); !ok || !errors.Is(opErr.Err, net.ErrClosed) { - t.Logf("server stopped err: %v", err) + t.Errorf("server stopped err: %v", err) } } }()