diff --git a/cmd/solana-exporter/collector.go b/cmd/solana-exporter/collector.go index 2eddcbd..7d566be 100644 --- a/cmd/solana-exporter/collector.go +++ b/cmd/solana-exporter/collector.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/asymmetric-research/solana-exporter/pkg/rpc" "github.com/asymmetric-research/solana-exporter/pkg/slog" "github.com/prometheus/client_golang/prometheus" @@ -39,6 +40,7 @@ type SolanaCollector struct { ValidatorDelinquent *GaugeDesc AccountBalances *GaugeDesc NodeVersion *GaugeDesc + NodeIdentity *GaugeDesc NodeIsHealthy *GaugeDesc NodeNumSlotsBehind *GaugeDesc NodeMinimumLedgerSlot *GaugeDesc @@ -80,6 +82,11 @@ func NewSolanaCollector(client *rpc.Client, config *ExporterConfig) *SolanaColle "Node version of solana", VersionLabel, ), + NodeIdentity: NewGaugeDesc( + "solana_node_identity", + "Node identity of solana", + VersionLabel, + ), NodeIsHealthy: NewGaugeDesc( "solana_node_is_healthy", "Whether the node is healthy", @@ -102,6 +109,7 @@ func NewSolanaCollector(client *rpc.Client, config *ExporterConfig) *SolanaColle func (c *SolanaCollector) Describe(ch chan<- *prometheus.Desc) { ch <- c.NodeVersion.Desc + ch <- c.NodeIdentity.Desc ch <- c.ValidatorActiveStake.Desc ch <- c.ValidatorLastVote.Desc ch <- c.ValidatorRootSlot.Desc @@ -158,6 +166,20 @@ func (c *SolanaCollector) collectVersion(ctx context.Context, ch chan<- promethe ch <- c.NodeVersion.MustNewConstMetric(1, version) c.logger.Info("Version collected.") } + +func (c *SolanaCollector) collectIdentity(ctx context.Context, ch chan<- prometheus.Metric) { + c.logger.Info("Collecting identity...") + identity, err := c.rpcClient.GetIdentity(ctx) + if err != nil { + c.logger.Errorf("failed to get identity: %v", err) + ch <- c.NodeIdentity.NewInvalidMetric(err) + return + } + + ch <- c.NodeIdentity.MustNewConstMetric(1, identity) + c.logger.Info("Identity collected.") +} + func (c *SolanaCollector) collectMinimumLedgerSlot(ctx context.Context, ch chan<- prometheus.Metric) { c.logger.Info("Collecting minimum ledger slot...") slot, err := c.rpcClient.GetMinimumLedgerSlot(ctx) @@ -170,6 +192,7 @@ func (c *SolanaCollector) collectMinimumLedgerSlot(ctx context.Context, ch chan< ch <- c.NodeMinimumLedgerSlot.MustNewConstMetric(float64(slot)) c.logger.Info("Minimum ledger slot collected.") } + func (c *SolanaCollector) collectFirstAvailableBlock(ctx context.Context, ch chan<- prometheus.Metric) { c.logger.Info("Collecting first available block...") block, err := c.rpcClient.GetFirstAvailableBlock(ctx) @@ -254,6 +277,7 @@ func (c *SolanaCollector) Collect(ch chan<- prometheus.Metric) { c.collectFirstAvailableBlock(ctx, ch) c.collectVoteAccounts(ctx, ch) c.collectVersion(ctx, ch) + c.collectIdentity(ctx, ch) c.collectBalances(ctx, ch) c.logger.Info("=========== END COLLECTION ===========") diff --git a/cmd/solana-exporter/collector_test.go b/cmd/solana-exporter/collector_test.go index d7e2f08..8b41e36 100644 --- a/cmd/solana-exporter/collector_test.go +++ b/cmd/solana-exporter/collector_test.go @@ -57,6 +57,7 @@ func NewSimulator(t *testing.T, slot int) (*Simulator, *rpc.Client) { mockServer, client := rpc.NewMockClient(t, map[string]any{ "getVersion": map[string]string{"solana-core": "v1.0.0"}, + "getIdentity": map[string]string{"identity": "testIdentity"}, "getLeaderSchedule": leaderSchedule, "getHealth": "ok", }, @@ -236,6 +237,9 @@ func TestSolanaCollector(t *testing.T) { collector.NodeVersion.makeCollectionTest( NewLV(1, "v1.0.0"), ), + collector.NodeIdentity.makeCollectionTest( + NewLV(1, "testIdentity"), + ), collector.NodeIsHealthy.makeCollectionTest( NewLV(1), ), diff --git a/pkg/rpc/client.go b/pkg/rpc/client.go index 0578819..b98b020 100644 --- a/pkg/rpc/client.go +++ b/pkg/rpc/client.go @@ -5,12 +5,13 @@ import ( "context" "encoding/json" "fmt" - "github.com/asymmetric-research/solana-exporter/pkg/slog" - "go.uber.org/zap" "io" "net/http" "slices" "time" + + "github.com/asymmetric-research/solana-exporter/pkg/slog" + "go.uber.org/zap" ) type ( @@ -133,6 +134,18 @@ func (c *Client) GetVersion(ctx context.Context) (string, error) { return resp.Result.Version, nil } +// GetIdentity returns the current Solana version running on the node. +// See API docs: https://solana.com/docs/rpc/http/getidentity +func (c *Client) GetIdentity(ctx context.Context) (string, error) { + var resp Response[struct { + Identity string `json:"identity"` + }] + if err := getResponse(ctx, c, "getIdentity", []any{}, &resp); err != nil { + return "", err + } + return resp.Result.Identity, nil +} + // GetSlot returns the slot that has reached the given or default commitment level. // See API docs: https://solana.com/docs/rpc/http/getslot func (c *Client) GetSlot(ctx context.Context, commitment Commitment) (int64, error) {