Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/tx_indexer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func main() {
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}
_ = internalMetrics.StartMetricsServer(metricsConfig, []string{internalMetrics.ServiceTxIndexer}, logger)

Expand Down
18 changes: 14 additions & 4 deletions cmd/verifier/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/hibiken/asynq"

Expand Down Expand Up @@ -82,21 +83,30 @@ func main() {
supportedChains,
)

// Initialize metrics based on configuration
var httpMetrics *internalMetrics.HTTPMetrics
var appStoreCollector *internalMetrics.AppStoreCollector
if cfg.Metrics.Enabled {
logger.Info("Metrics enabled, setting up Prometheus metrics")

// Start metrics HTTP server with HTTP metrics
services := []string{internalMetrics.ServiceHTTP}
services := []string{internalMetrics.ServiceHTTP, internalMetrics.ServiceAppStore}
_ = internalMetrics.StartMetricsServer(internalMetrics.Config{
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}, services, logger)

// Create HTTP metrics implementation
httpMetrics = internalMetrics.NewHTTPMetrics()

appStoreMetrics := internalMetrics.NewAppStoreMetrics()
appStoreCollector = internalMetrics.NewAppStoreCollector(db, appStoreMetrics, logger, 30*time.Second)
appStoreCollector.Start()

defer func() {
if appStoreCollector != nil {
appStoreCollector.Stop()
}
}()
} else {
logger.Info("Verifier metrics disabled")
}
Expand Down
4 changes: 1 addition & 3 deletions cmd/worker/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,20 +129,18 @@ func main() {
vaultMgmService,
)

// Initialize metrics based on configuration
var workerMetrics internalMetrics.WorkerMetricsInterface
if cfg.Metrics.Enabled {
logger.Info("Metrics enabled, setting up Prometheus metrics")

// Start metrics HTTP server with worker metrics
services := []string{internalMetrics.ServiceWorker}
_ = internalMetrics.StartMetricsServer(internalMetrics.Config{
Enabled: true,
Host: cfg.Metrics.Host,
Port: cfg.Metrics.Port,
Token: cfg.Metrics.Token,
}, services, logger)

// Create worker metrics instance
workerMetrics = internalMetrics.NewWorkerMetrics()
} else {
logger.Info("Worker metrics disabled")
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type MetricsConfig struct {
Enabled bool `mapstructure:"enabled" json:"enabled,omitempty"`
Host string `mapstructure:"host" json:"host,omitempty"`
Port int `mapstructure:"port" json:"port,omitempty"`
Token string `mapstructure:"token" json:"token,omitempty"`
}

type PluginAssetsConfig struct {
Expand Down
18 changes: 3 additions & 15 deletions internal/api/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,28 +71,21 @@ func (s *Server) CreatePluginPolicy(c echo.Context) error {
if policy.ID == uuid.Nil {
policy.ID = uuid.New()
}
publicKey, ok := c.Get("vault_public_key").(string)
if !ok || publicKey == "" {
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgVaultPublicKeyGetFailed))
}
if policy.PublicKey != publicKey {
return c.JSON(http.StatusForbidden, NewErrorResponseWithMessage(msgPublicKeyMismatch))
}

var (
isTrialActive bool
err error
)
err = s.db.WithTransaction(c.Request().Context(), func(ctx context.Context, tx pgx.Tx) error {
isTrialActive, _, err = s.db.IsTrialActive(ctx, tx, publicKey)
isTrialActive, _, err = s.db.IsTrialActive(ctx, tx, policy.PublicKey)
return err
})
if err != nil {
s.logger.WithError(err).Warnf("Failed to check trial info")
}

if !isTrialActive {
filePathName := common.GetVaultBackupFilename(publicKey, vtypes.PluginVultisigFees_feee.String())
filePathName := common.GetVaultBackupFilename(policy.PublicKey, vtypes.PluginVultisigFees_feee.String())
exist, err := s.vaultStorage.Exist(filePathName)
if err != nil {
s.logger.WithError(err).Error("failed to check vault existence")
Expand Down Expand Up @@ -197,18 +190,13 @@ func (s *Server) UpdatePluginPolicyById(c echo.Context) error {
return c.JSON(http.StatusBadRequest, NewErrorResponseWithMessage(msgRequestParseFailed))
}

publicKey, ok := c.Get("vault_public_key").(string)
if !ok || publicKey == "" {
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgVaultPublicKeyGetFailed))
}

oldPolicy, err := s.policyService.GetPluginPolicy(c.Request().Context(), policy.ID)
if err != nil {
s.logger.WithError(err).Errorf("failed to get plugin policy, id:%s", policy.ID)
return c.JSON(http.StatusInternalServerError, NewErrorResponseWithMessage(msgPolicyGetFailed))
}

if oldPolicy.PublicKey != publicKey || policy.PublicKey != publicKey {
if oldPolicy.PublicKey != policy.PublicKey {
return c.JSON(http.StatusForbidden, NewErrorResponseWithMessage(msgPublicKeyMismatch))
}

Expand Down
26 changes: 13 additions & 13 deletions internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,27 +160,27 @@ func (s *Server) StartServer() error {
tokenGroup.GET("", s.GetActiveTokens)

// Protected vault endpoints
vaultGroup := e.Group("/vault", s.VaultAuthMiddleware)
// Reshare vault endpoint, only user who already log in can request resharing
vaultGroup := e.Group("/vault")
// Reshare vault endpoint
vaultGroup.POST("/reshare", s.ReshareVault)
vaultGroup.GET("/get/:pluginId/:publicKeyECDSA", s.GetVault) // Get Vault Data
vaultGroup.GET("/exist/:pluginId/:publicKeyECDSA", s.ExistVault) // Check if Vault exists
vaultGroup.GET("/get/:pluginId/:publicKeyECDSA", s.GetVault, s.VaultAuthMiddleware) // Get Vault Data (secured)
vaultGroup.GET("/exist/:pluginId/:publicKeyECDSA", s.ExistVault) // Check if Vault exists

// Sign endpoint, plugin should authenticate themselves using the API Key issued by the Verifier
pluginSigner := e.Group("/plugin-signer", s.PluginAuthMiddleware)
pluginSigner.POST("/sign", s.SignPluginMessages) // Sign messages
pluginSigner.GET("/sign/response/:taskId", s.GetKeysignResult) // Get keysign result

pluginGroup := e.Group("/plugin", s.VaultAuthMiddleware)
pluginGroup.DELETE("/:pluginId", s.DeletePlugin) // Delete plugin
pluginGroup.POST("/policy", s.CreatePluginPolicy)
pluginGroup := e.Group("/plugin")
pluginGroup.DELETE("/:pluginId", s.DeletePlugin, s.VaultAuthMiddleware)
pluginGroup.POST("/policy", s.CreatePluginPolicy) // Every valid request should be signed
pluginGroup.PUT("/policy", s.UpdatePluginPolicyById)
pluginGroup.GET("/policies/:pluginId", s.GetAllPluginPolicies)
pluginGroup.GET("/policy/:policyId", s.GetPluginPolicyById)
pluginGroup.GET("/policy/:pluginId/total-count", s.GetPluginInstallationsCountByID)
pluginGroup.DELETE("/policy/:policyId", s.DeletePluginPolicyById)
pluginGroup.GET("/policies/:policyId/history", s.GetPluginPolicyTransactionHistory)
pluginGroup.GET("/transactions", s.GetPluginTransactionHistory)
pluginGroup.GET("/policies/:pluginId", s.GetAllPluginPolicies, s.VaultAuthMiddleware)
pluginGroup.GET("/policy/:policyId", s.GetPluginPolicyById, s.VaultAuthMiddleware)
pluginGroup.GET("/policy/:pluginId/total-count", s.GetPluginInstallationsCountByID, s.VaultAuthMiddleware)
pluginGroup.DELETE("/policy/:policyId", s.DeletePluginPolicyById, s.VaultAuthMiddleware)
pluginGroup.GET("/policies/:policyId/history", s.GetPluginPolicyTransactionHistory, s.VaultAuthMiddleware)
pluginGroup.GET("/transactions", s.GetPluginTransactionHistory, s.VaultAuthMiddleware)

// fee group. These should only be accessible by the plugin server
feeGroup := e.Group("/fees", s.PluginAuthMiddleware)
Expand Down
121 changes: 121 additions & 0 deletions internal/metrics/appstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package metrics

import (
"time"

"github.com/prometheus/client_golang/prometheus"
)

var (
appstoreInstallationsTotal = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "installations_total",
Help: "Current number of installations per plugin",
},
[]string{"plugin_id"},
)

appstorePoliciesTotal = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "policies_total",
Help: "Current number of active policies per plugin",
},
[]string{"plugin_id"},
)

appstoreFeesEarnedTotal = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "fees_earned_total",
Help: "Total fees earned per plugin in smallest unit",
},
[]string{"plugin_id"},
)

appstoreInstallationsGrandTotal = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "installations_grand_total",
Help: "Total installations across all plugins",
},
)

appstorePoliciesGrandTotal = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "policies_grand_total",
Help: "Total active policies across all plugins",
},
)

appstoreFeesGrandTotal = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "fees_grand_total",
Help: "Total fees earned across all plugins in smallest unit",
},
)

appstoreCollectorLastUpdateTimestamp = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "verifier",
Subsystem: "appstore",
Name: "collector_last_update_timestamp",
Help: "Unix timestamp of last successful metrics update",
},
)
)

type AppStoreMetrics struct{}

func NewAppStoreMetrics() *AppStoreMetrics {
return &AppStoreMetrics{}
}

func (a *AppStoreMetrics) UpdateInstallations(data map[string]int64) {
appstoreInstallationsTotal.Reset()

var grandTotal int64
for pluginID, count := range data {
appstoreInstallationsTotal.WithLabelValues(pluginID).Set(float64(count))
grandTotal += count
}

appstoreInstallationsGrandTotal.Set(float64(grandTotal))
}

func (a *AppStoreMetrics) UpdatePolicies(data map[string]int64) {
appstorePoliciesTotal.Reset()

var grandTotal int64
for pluginID, count := range data {
appstorePoliciesTotal.WithLabelValues(pluginID).Set(float64(count))
grandTotal += count
}

appstorePoliciesGrandTotal.Set(float64(grandTotal))
}

func (a *AppStoreMetrics) UpdateFees(data map[string]int64) {
appstoreFeesEarnedTotal.Reset()

var grandTotal int64
for pluginID, total := range data {
appstoreFeesEarnedTotal.WithLabelValues(pluginID).Set(float64(total))
grandTotal += total
}

appstoreFeesGrandTotal.Set(float64(grandTotal))
}

func (a *AppStoreMetrics) UpdateTimestamp() {
appstoreCollectorLastUpdateTimestamp.Set(float64(time.Now().Unix()))
}
95 changes: 95 additions & 0 deletions internal/metrics/appstore_collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package metrics

import (
"context"
"sync"
"time"

"github.com/sirupsen/logrus"
)

type DatabaseQuerier interface {
GetInstallationsByPlugin(ctx context.Context) (map[string]int64, error)
GetPoliciesByPlugin(ctx context.Context) (map[string]int64, error)
GetFeesByPlugin(ctx context.Context) (map[string]int64, error)
}

type AppStoreCollector struct {
db DatabaseQuerier
metrics *AppStoreMetrics
logger *logrus.Logger
interval time.Duration
stopCh chan struct{}
doneCh chan struct{}
stopOnce sync.Once
}

func NewAppStoreCollector(db DatabaseQuerier, metrics *AppStoreMetrics, logger *logrus.Logger, interval time.Duration) *AppStoreCollector {
return &AppStoreCollector{
db: db,
metrics: metrics,
logger: logger,
interval: interval,
stopCh: make(chan struct{}),
doneCh: make(chan struct{}),
}
}

func (c *AppStoreCollector) Start() {
c.logger.Info("Starting App Store metrics collector")

go func() {
defer close(c.doneCh)

ticker := time.NewTicker(c.interval)
defer ticker.Stop()

c.collect()

for {
select {
case <-ticker.C:
c.collect()
case <-c.stopCh:
c.logger.Info("Stopping App Store metrics collector")
return
}
}
}()
}

func (c *AppStoreCollector) Stop() {
c.stopOnce.Do(func() {
close(c.stopCh)
<-c.doneCh
})
}

func (c *AppStoreCollector) collect() {
ctx, cancel := context.WithTimeout(context.Background(), c.interval*9/10)
defer cancel()

installations, err := c.db.GetInstallationsByPlugin(ctx)
if err != nil {
c.logger.Errorf("Failed to collect installations: %v", err)
} else {
c.metrics.UpdateInstallations(installations)
c.metrics.UpdateTimestamp()
}

policies, err := c.db.GetPoliciesByPlugin(ctx)
if err != nil {
c.logger.Errorf("Failed to collect policies: %v", err)
} else {
c.metrics.UpdatePolicies(policies)
c.metrics.UpdateTimestamp()
}

fees, err := c.db.GetFeesByPlugin(ctx)
if err != nil {
c.logger.Errorf("Failed to collect fees: %v", err)
} else {
c.metrics.UpdateFees(fees)
c.metrics.UpdateTimestamp()
}
}
Loading