From d06edc078e31b4d10ca1d1ee380835d1b11ebc1f Mon Sep 17 00:00:00 2001 From: Matt Johnstone Date: Tue, 29 Oct 2024 00:15:21 +0200 Subject: [PATCH] refactored WatchSlots_Dynamic test --- cmd/solana_exporter/config_test.go | 6 +- cmd/solana_exporter/desc_test.go | 4 - cmd/solana_exporter/exporter.go | 4 +- cmd/solana_exporter/exporter_test.go | 191 ++++++++++++++------------- cmd/solana_exporter/slots.go | 16 +-- cmd/solana_exporter/slots_test.go | 160 +++++++++++++++------- cmd/solana_exporter/utils.go | 4 +- cmd/solana_exporter/utils_test.go | 21 ++- pkg/rpc/mock.go | 18 +-- pkg/slog/logger.go | 4 + 10 files changed, 248 insertions(+), 180 deletions(-) diff --git a/cmd/solana_exporter/config_test.go b/cmd/solana_exporter/config_test.go index b494bad..433a08b 100644 --- a/cmd/solana_exporter/config_test.go +++ b/cmd/solana_exporter/config_test.go @@ -28,21 +28,21 @@ func TestNewExporterConfig(t *testing.T) { httpTimeout: 60 * time.Second, rpcUrl: simulator.Server.URL(), listenAddress: ":8080", - nodeKeys: nodekeys, + nodeKeys: simulator.Nodekeys, balanceAddresses: []string{"xxx", "yyy", "zzz"}, comprehensiveSlotTracking: false, monitorBlockSizes: false, lightMode: false, slotPace: time.Second, wantErr: false, - expectedVoteKeys: votekeys, + expectedVoteKeys: simulator.Votekeys, }, { name: "light mode with incompatible options", httpTimeout: 60 * time.Second, rpcUrl: simulator.Server.URL(), listenAddress: ":8080", - nodeKeys: nodekeys, + nodeKeys: simulator.Nodekeys, balanceAddresses: []string{"xxx", "yyy", "zzz"}, comprehensiveSlotTracking: true, monitorBlockSizes: false, diff --git a/cmd/solana_exporter/desc_test.go b/cmd/solana_exporter/desc_test.go index ab8f0b6..2d60ef5 100644 --- a/cmd/solana_exporter/desc_test.go +++ b/cmd/solana_exporter/desc_test.go @@ -41,7 +41,3 @@ func (c *GaugeDesc) expectedCollection(labeledValues ...LV) string { func (c *GaugeDesc) makeCollectionTest(labeledValues ...LV) collectionTest { return collectionTest{Name: c.Name, ExpectedResponse: c.expectedCollection(labeledValues...)} } - -func abcValues(a, b, c float64) []LV { - return []LV{NewLV(a, "aaa", "AAA"), NewLV(b, "bbb", "BBB"), NewLV(c, "ccc", "CCC")} -} diff --git a/cmd/solana_exporter/exporter.go b/cmd/solana_exporter/exporter.go index 266f6f3..12e1906 100644 --- a/cmd/solana_exporter/exporter.go +++ b/cmd/solana_exporter/exporter.go @@ -25,8 +25,8 @@ const ( StateCurrent = "current" StateDelinquent = "delinquent" - TransactionTypeVote = "vote" - TransactionTypeTotal = "total" + TransactionTypeVote = "vote" + TransactionTypeNonVote = "non_vote" ) type SolanaCollector struct { diff --git a/cmd/solana_exporter/exporter_test.go b/cmd/solana_exporter/exporter_test.go index 6ee7d1a..a221650 100644 --- a/cmd/solana_exporter/exporter_test.go +++ b/cmd/solana_exporter/exporter_test.go @@ -17,6 +17,9 @@ import ( "time" ) +const InflationRewardLamports = 10 +const FeeRewardLamports = 10 + type ( Simulator struct { Server *rpc.MockServer @@ -24,113 +27,80 @@ type ( Slot int BlockHeight int Epoch int - EpochSize int - SlotTime time.Duration TransactionCount int - LeaderSchedule map[string][]int + + SlotTime time.Duration + EpochSize int + LeaderSchedule map[string][]int + Nodekeys []string + Votekeys []string } ) -var ( - nodekeys = []string{"aaa", "bbb", "ccc"} - votekeys = []string{"AAA", "BBB", "CCC"} - balances = map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3, "AAA": 4, "BBB": 5, "CCC": 6} - rawVoteAccounts = map[string]any{ - "current": []map[string]any{ - { - "activatedStake": 42, - "lastVote": 147, - "nodePubkey": "bbb", - "rootSlot": 18, - "votePubkey": "BBB", - }, - { - "activatedStake": 43, - "lastVote": 148, - "nodePubkey": "ccc", - "rootSlot": 19, - "votePubkey": "CCC", - }, - }, - "delinquent": []map[string]any{ - { - "activatedStake": 49, - "lastVote": 92, - "nodePubkey": "aaa", - "rootSlot": 3, - "votePubkey": "AAA", - }, - }, - } - rawBalances = map[string]int{ - "aaa": 1 * rpc.LamportsInSol, - "bbb": 2 * rpc.LamportsInSol, - "ccc": 3 * rpc.LamportsInSol, - "AAA": 4 * rpc.LamportsInSol, - "BBB": 5 * rpc.LamportsInSol, - "CCC": 6 * rpc.LamportsInSol, - } - balanceMetricResponse = ` -# HELP solana_account_balance Solana account balances, grouped by address -# TYPE solana_account_balance gauge -solana_account_balance{address="AAA"} 4 -solana_account_balance{address="BBB"} 5 -solana_account_balance{address="CCC"} 6 -solana_account_balance{address="aaa"} 1 -solana_account_balance{address="bbb"} 2 -solana_account_balance{address="ccc"} 3 -` - dynamicLeaderSchedule = map[string][]int{ - "aaa": {0, 1, 2, 3, 12, 13, 14, 15}, - "bbb": {4, 5, 6, 7, 16, 17, 18, 19}, - "ccc": {8, 9, 10, 11, 20, 21, 22, 23}, - } -) - -/* -===== DYNAMIC CLIENT =====: -*/ - func voteTx(nodekey string) []string { return []string{nodekey, strings.ToUpper(nodekey), VoteProgram} } func NewSimulator(t *testing.T, slot int) (*Simulator, *rpc.Client) { + nodekeys := []string{"aaa", "bbb", "ccc"} + votekeys := []string{"AAA", "BBB", "CCC"} + validatorInfos := make(map[string]rpc.MockValidatorInfo) - for _, nodekey := range nodekeys { + for i, nodekey := range nodekeys { validatorInfos[nodekey] = rpc.MockValidatorInfo{ - Votekey: strings.ToUpper(nodekey), + Votekey: votekeys[i], Stake: 1_000_000, Delinquent: false, } } + leaderSchedule := map[string][]int{ + "aaa": {0, 1, 2, 3, 12, 13, 14, 15}, + "bbb": {4, 5, 6, 7, 16, 17, 18, 19}, + "ccc": {8, 9, 10, 11, 20, 21, 22, 23}, + } mockServer, client := rpc.NewMockClient(t, map[string]any{ "getVersion": map[string]string{"solana-core": "v1.0.0"}, - "getLeaderSchedule": dynamicLeaderSchedule, + "getLeaderSchedule": leaderSchedule, "getHealth": "ok", }, - rawBalances, - map[string]int{"AAA": 10, "BBB": 10, "CCC": 10}, + map[string]int{ + "aaa": 1 * rpc.LamportsInSol, + "bbb": 2 * rpc.LamportsInSol, + "ccc": 3 * rpc.LamportsInSol, + "AAA": 4 * rpc.LamportsInSol, + "BBB": 5 * rpc.LamportsInSol, + "CCC": 6 * rpc.LamportsInSol, + }, + map[string]int{ + "AAA": InflationRewardLamports, + "BBB": InflationRewardLamports, + "CCC": InflationRewardLamports, + }, nil, validatorInfos, ) - server := Simulator{ + simulator := Simulator{ Slot: 0, Server: mockServer, EpochSize: 24, SlotTime: 100 * time.Millisecond, - LeaderSchedule: dynamicLeaderSchedule, + LeaderSchedule: leaderSchedule, + Nodekeys: nodekeys, + Votekeys: votekeys, } - server.PopulateSlot(0) - for { - server.Slot++ - server.PopulateSlot(server.Slot) - if server.Slot == slot { - break + simulator.PopulateSlot(0) + if slot > 0 { + for { + simulator.Slot++ + simulator.PopulateSlot(simulator.Slot) + if simulator.Slot == slot { + break + } } } - return &server, client + + return &simulator, client } func (c *Simulator) Run(ctx context.Context) { @@ -173,7 +143,7 @@ func (c *Simulator) PopulateSlot(slot int) { {"xxx", "yyy", "zzz"}, } // assume all validators voted - for _, nodekey := range nodekeys { + for _, nodekey := range c.Nodekeys { transactions = append(transactions, voteTx(nodekey)) info := c.Server.GetValidatorInfo(nodekey) info.LastVote = slot @@ -181,7 +151,7 @@ func (c *Simulator) PopulateSlot(slot int) { } c.TransactionCount += len(transactions) - block = &rpc.MockBlockInfo{Fee: 100, Transactions: transactions} + block = &rpc.MockBlockInfo{Fee: FeeRewardLamports, Transactions: transactions} } // add slot info: c.Server.SetOpt(rpc.SlotInfosOpt, slot, rpc.MockSlotInfo{Leader: leader, Block: block}) @@ -251,17 +221,17 @@ func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases } } -func newTestConfig(fast bool) *ExporterConfig { +func newTestConfig(simulator *Simulator, fast bool) *ExporterConfig { pace := time.Duration(100) * time.Second if fast { pace = time.Duration(500) * time.Millisecond } config := ExporterConfig{ time.Second * time.Duration(1), - "http://localhost:8899", + simulator.Server.URL(), ":8080", - nodekeys, - votekeys, + simulator.Nodekeys, + simulator.Votekeys, nil, true, true, @@ -272,19 +242,54 @@ func newTestConfig(fast bool) *ExporterConfig { } func TestSolanaCollector(t *testing.T) { - _, client := NewSimulator(t, 35) - collector := NewSolanaCollector(client, newTestConfig(false)) + simulator, client := NewSimulator(t, 35) + collector := NewSolanaCollector(client, newTestConfig(simulator, false)) prometheus.NewPedanticRegistry().MustRegister(collector) testCases := []collectionTest{ - collector.ValidatorActiveStake.makeCollectionTest(abcValues(1_000_000, 1_000_000, 1_000_000)...), - collector.ValidatorLastVote.makeCollectionTest(abcValues(34, 34, 34)...), - collector.ValidatorRootSlot.makeCollectionTest(abcValues(0, 0, 0)...), - collector.ValidatorDelinquent.makeCollectionTest(abcValues(0, 0, 0)...), - {Name: "solana_account_balance", ExpectedResponse: balanceMetricResponse}, - collector.NodeVersion.makeCollectionTest(NewLV(1, "v1.0.0")), - collector.NodeIsHealthy.makeCollectionTest(NewLV(1)), - collector.NodeNumSlotsBehind.makeCollectionTest(NewLV(0)), + collector.ValidatorActiveStake.makeCollectionTest( + NewLV(1_000_000, "aaa", "AAA"), + NewLV(1_000_000, "bbb", "BBB"), + NewLV(1_000_000, "ccc", "CCC"), + ), + collector.ValidatorLastVote.makeCollectionTest( + NewLV(34, "aaa", "AAA"), + NewLV(34, "bbb", "BBB"), + NewLV(34, "ccc", "CCC"), + ), + collector.ValidatorRootSlot.makeCollectionTest( + NewLV(0, "aaa", "AAA"), + NewLV(0, "bbb", "BBB"), + NewLV(0, "ccc", "CCC"), + ), + collector.ValidatorDelinquent.makeCollectionTest( + NewLV(0, "aaa", "AAA"), + NewLV(0, "bbb", "BBB"), + NewLV(0, "ccc", "CCC"), + ), + collector.NodeVersion.makeCollectionTest( + NewLV(1, "v1.0.0"), + ), + collector.NodeIsHealthy.makeCollectionTest( + NewLV(1), + ), + collector.NodeNumSlotsBehind.makeCollectionTest( + NewLV(0), + ), + collector.AccountBalances.makeCollectionTest( + NewLV(4, "AAA"), + NewLV(5, "BBB"), + NewLV(6, "CCC"), + NewLV(1, "aaa"), + NewLV(2, "bbb"), + NewLV(3, "ccc"), + ), + collector.NodeMinimumLedgerSlot.makeCollectionTest( + NewLV(11), + ), + collector.NodeFirstAvailableBlock.makeCollectionTest( + NewLV(11), + ), } runCollectionTests(t, collector, testCases) diff --git a/cmd/solana_exporter/slots.go b/cmd/solana_exporter/slots.go index e8bd937..a0df68c 100644 --- a/cmd/solana_exporter/slots.go +++ b/cmd/solana_exporter/slots.go @@ -80,7 +80,7 @@ func NewSlotWatcher(client *rpc.Client, config *ExporterConfig) *SlotWatcher { NodekeyLabel, SkipStatusLabel, StatusValid, StatusSkipped, ), }, - []string{SkipStatusLabel, NodekeyLabel}, + []string{NodekeyLabel, SkipStatusLabel}, ), LeaderSlotsByEpochMetric: prometheus.NewCounterVec( prometheus.CounterOpts{ @@ -90,7 +90,7 @@ func NewSlotWatcher(client *rpc.Client, config *ExporterConfig) *SlotWatcher { NodekeyLabel, SkipStatusLabel, StatusValid, StatusSkipped, EpochLabel, ), }, - []string{SkipStatusLabel, NodekeyLabel, EpochLabel}, + []string{NodekeyLabel, EpochLabel, SkipStatusLabel}, ), InflationRewardsMetric: prometheus.NewGaugeVec( prometheus.GaugeOpts{ @@ -314,13 +314,13 @@ func (c *SlotWatcher) fetchAndEmitBlockProduction(ctx context.Context, endSlot i valid := float64(production.BlocksProduced) skipped := float64(production.LeaderSlots - production.BlocksProduced) - c.LeaderSlotsMetric.WithLabelValues(StatusValid, address).Add(valid) - c.LeaderSlotsMetric.WithLabelValues(StatusSkipped, address).Add(skipped) + c.LeaderSlotsMetric.WithLabelValues(address, StatusValid).Add(valid) + c.LeaderSlotsMetric.WithLabelValues(address, StatusSkipped).Add(skipped) if slices.Contains(c.config.NodeKeys, address) || c.config.ComprehensiveSlotTracking { epochStr := toString(c.currentEpoch) - c.LeaderSlotsByEpochMetric.WithLabelValues(StatusValid, address, epochStr).Add(valid) - c.LeaderSlotsByEpochMetric.WithLabelValues(StatusSkipped, address, epochStr).Add(skipped) + c.LeaderSlotsByEpochMetric.WithLabelValues(address, epochStr, StatusValid).Add(valid) + c.LeaderSlotsByEpochMetric.WithLabelValues(address, epochStr, StatusSkipped).Add(skipped) } } @@ -395,15 +395,15 @@ func (c *SlotWatcher) fetchAndEmitSingleBlockInfo( // track block size: if c.config.MonitorBlockSizes { - c.BlockSizeMetric.WithLabelValues(nodekey, TransactionTypeTotal).Set(float64(len(block.Transactions))) // now count and emit votes: voteCount, err := CountVoteTransactions(block) if err != nil { return err } c.BlockSizeMetric.WithLabelValues(nodekey, TransactionTypeVote).Set(float64(voteCount)) + nonVoteCount := len(block.Transactions) - voteCount + c.BlockSizeMetric.WithLabelValues(nodekey, TransactionTypeNonVote).Set(float64(nonVoteCount)) } - return nil } diff --git a/cmd/solana_exporter/slots_test.go b/cmd/solana_exporter/slots_test.go index 3a5af79..d80934d 100644 --- a/cmd/solana_exporter/slots_test.go +++ b/cmd/solana_exporter/slots_test.go @@ -17,6 +17,7 @@ type slotMetricValues struct { EpochNumber float64 EpochFirstSlot float64 EpochLastSlot float64 + BlockHeight float64 } func getSlotMetricValues(watcher *SlotWatcher) slotMetricValues { @@ -26,45 +27,58 @@ func getSlotMetricValues(watcher *SlotWatcher) slotMetricValues { EpochNumber: testutil.ToFloat64(watcher.EpochNumberMetric), EpochFirstSlot: testutil.ToFloat64(watcher.EpochFirstSlotMetric), EpochLastSlot: testutil.ToFloat64(watcher.EpochLastSlotMetric), + BlockHeight: testutil.ToFloat64(watcher.BlockHeightMetric), } } func assertSlotMetricsChangeCorrectly(t *testing.T, initial slotMetricValues, final slotMetricValues) { // make sure that things have increased - assert.Greaterf( - t, + assert.Greaterf(t, final.SlotHeight, initial.SlotHeight, "Slot has not increased! (%v -> %v)", - initial.SlotHeight, - final.SlotHeight, + initial.SlotHeight, final.SlotHeight, ) - assert.Greaterf( - t, + assert.Greaterf(t, final.TotalTransactions, initial.TotalTransactions, "Total transactions have not increased! (%v -> %v)", - initial.TotalTransactions, - final.TotalTransactions, + initial.TotalTransactions, final.TotalTransactions, ) - assert.GreaterOrEqualf( - t, + assert.GreaterOrEqualf(t, final.EpochNumber, initial.EpochNumber, "Epoch number has decreased! (%v -> %v)", - initial.EpochNumber, - final.EpochNumber, + initial.EpochNumber, final.EpochNumber, + ) + assert.GreaterOrEqualf(t, + final.EpochFirstSlot, + initial.EpochFirstSlot, + "Epoch first slot has decreased! (%v -> %v)", + initial.EpochFirstSlot, final.EpochFirstSlot, + ) + assert.GreaterOrEqualf(t, + final.EpochLastSlot, + initial.EpochLastSlot, + "Epoch last slot has decreased! (%v -> %v)", + initial.EpochLastSlot, final.EpochLastSlot, + ) + assert.Greaterf(t, + final.BlockHeight, + initial.BlockHeight, + "Block height has decreased! (%v -> %v)", + initial.BlockHeight, final.BlockHeight, ) } func TestSlotWatcher_WatchSlots_Static(t *testing.T) { - ctx := context.Background() + // TODO: is this test necessary? If not - remove, else, could definitely do with a clean. - config := newTestConfig(true) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - _, client := NewSimulator(t, 35) - - watcher := NewSlotWatcher(client, config) + simulator, client := NewSimulator(t, 35) + watcher := NewSlotWatcher(client, newTestConfig(simulator, true)) // reset metrics before running tests: watcher.LeaderSlotsMetric.Reset() watcher.LeaderSlotsByEpochMetric.Reset() @@ -94,7 +108,7 @@ func TestSlotWatcher_WatchSlots_Static(t *testing.T) { } // add inflation reward tests: - inflationRewards, err := client.GetInflationReward(ctx, rpc.CommitmentFinalized, votekeys, 2) + inflationRewards, err := client.GetInflationReward(ctx, rpc.CommitmentFinalized, simulator.Votekeys, 2) assert.NoError(t, err) for i, rewardInfo := range inflationRewards { epoch := fmt.Sprintf("%v", epochInfo.Epoch) @@ -102,7 +116,7 @@ func TestSlotWatcher_WatchSlots_Static(t *testing.T) { tests, testCase{ expectedValue: float64(rewardInfo.Amount) / float64(rpc.LamportsInSol), - metric: watcher.InflationRewardsMetric.WithLabelValues(votekeys[i], epoch), + metric: watcher.InflationRewardsMetric.WithLabelValues(simulator.Votekeys[i], epoch), }, ) } @@ -116,24 +130,23 @@ func TestSlotWatcher_WatchSlots_Static(t *testing.T) { } func TestSlotWatcher_WatchSlots_Dynamic(t *testing.T) { + // TODO: figure out how to get rid of the error logs that happen when this test closes. + // This is presumably due to the context cancelling mid-execution of a WatchSlots() iteration + // create clients: - server, client := NewSimulator(t, 35) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - config := newTestConfig(true) - collector := NewSolanaCollector(client, config) - watcher := NewSlotWatcher(client, config) + simulator, client := NewSimulator(t, 23) + watcher := NewSlotWatcher(client, newTestConfig(simulator, true)) // reset metrics before running tests: watcher.LeaderSlotsMetric.Reset() watcher.LeaderSlotsByEpochMetric.Reset() - prometheus.NewPedanticRegistry().MustRegister(collector) // start client/collector and wait a bit: - - go server.Run(ctx) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go watcher.WatchSlots(ctx) time.Sleep(time.Second) - go watcher.WatchSlots(ctx) + go simulator.Run(ctx) time.Sleep(time.Second) initial := getSlotMetricValues(watcher) @@ -149,34 +162,87 @@ func TestSlotWatcher_WatchSlots_Dynamic(t *testing.T) { assertSlotMetricsChangeCorrectly(t, initial, final) // sense check to make sure the exporter is not "ahead" of the client (due to double counting or whatever) - assert.LessOrEqualf( - t, + assert.LessOrEqualf(t, int(final.SlotHeight), - server.Slot, - "Exporter slot (%v) ahead of client slot (%v)!", - int(final.SlotHeight), - server.Slot, + simulator.Slot, + "Exporter slot (%v) ahead of simulator slot (%v)!", + int(final.SlotHeight), simulator.Slot, ) - assert.LessOrEqualf( - t, + assert.LessOrEqualf(t, int(final.TotalTransactions), - server.TransactionCount, - "Exporter transaction count (%v) ahead of client transaction count (%v)!", - int(final.TotalTransactions), - server.TransactionCount, + simulator.TransactionCount, + "Exporter transaction count (%v) ahead of simulator transaction count (%v)!", + int(final.TotalTransactions), simulator.TransactionCount, ) - assert.LessOrEqualf( - t, + assert.LessOrEqualf(t, int(final.EpochNumber), - server.Epoch, - "Exporter epoch (%v) ahead of client epoch (%v)!", - int(final.EpochNumber), - server.Epoch, + simulator.Epoch, + "Exporter epoch (%v) ahead of simulator epoch (%v)!", + int(final.EpochNumber), simulator.Epoch, ) + // check block sizes (should always be the same due to simulator design: + for _, nodekey := range simulator.Nodekeys { + assert.Equalf(t, + float64(2), + testutil.ToFloat64(watcher.BlockSizeMetric.WithLabelValues(nodekey, TransactionTypeNonVote)), + "Incorrect %s block size for %s", + TransactionTypeNonVote, nodekey, + ) + assert.Equalf(t, + float64(3), + testutil.ToFloat64(watcher.BlockSizeMetric.WithLabelValues(nodekey, TransactionTypeVote)), + "Incorrect %s block size for %s", + TransactionTypeVote, nodekey, + ) + } + // check if epoch changed if final.EpochNumber > initial.EpochNumber { epochChanged = true + + // run some tests for the previous epoch: + leaderSlotsPerEpoch := simulator.EpochSize / len(simulator.Nodekeys) + for i, nodekey := range simulator.Nodekeys { + // leader slots per epoch: + assert.Equalf(t, + float64(leaderSlotsPerEpoch*3/4), + testutil.ToFloat64( + watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, toString(initial.EpochNumber), StatusValid), + ), + "Incorrect %s leader slots for %s at epoch %v", + StatusValid, nodekey, initial.EpochNumber, + ) + assert.Equalf(t, + float64(leaderSlotsPerEpoch*1/4), + testutil.ToFloat64( + watcher.LeaderSlotsByEpochMetric.WithLabelValues(nodekey, toString(initial.EpochNumber), StatusSkipped), + ), + "Incorrect %s leader slots for %s at epoch %v", + StatusSkipped, nodekey, initial.EpochNumber, + ) + + // inflation rewards: + votekey := simulator.Votekeys[i] + assert.Equalf(t, + float64(InflationRewardLamports)/rpc.LamportsInSol, + testutil.ToFloat64( + watcher.InflationRewardsMetric.WithLabelValues(votekey, toString(initial.EpochNumber)), + ), + "Incorrect inflation reward for %s at epoch %v", + votekey, initial.EpochNumber, + ) + + // fee rewards: + assert.Equalf(t, + float64(FeeRewardLamports*leaderSlotsPerEpoch*3/4)/rpc.LamportsInSol, + testutil.ToFloat64( + watcher.FeeRewardsMetric.WithLabelValues(nodekey, toString(initial.EpochNumber)), + ), + "Incorrect fee reward for %s at epoch %v", + nodekey, initial.EpochNumber, + ) + } } // make current final the new initial (for next iteration) diff --git a/cmd/solana_exporter/utils.go b/cmd/solana_exporter/utils.go index dd13894..12c7ca2 100644 --- a/cmd/solana_exporter/utils.go +++ b/cmd/solana_exporter/utils.go @@ -18,8 +18,8 @@ func assertf(condition bool, format string, args ...any) { } } -// toString is just a simple utility function for converting int -> string -func toString(i int64) string { +// toString is just a simple utility function for converting to strings +func toString(i any) string { return fmt.Sprintf("%v", i) } diff --git a/cmd/solana_exporter/utils_test.go b/cmd/solana_exporter/utils_test.go index 40dc5c1..8225823 100644 --- a/cmd/solana_exporter/utils_test.go +++ b/cmd/solana_exporter/utils_test.go @@ -57,31 +57,28 @@ func TestCombineUnique(t *testing.T) { } func TestFetchBalances(t *testing.T) { - _, client := rpc.NewMockClient(t, nil, rawBalances, nil, nil, nil) + simulator, client := NewSimulator(t, 0) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - fetchedBalances, err := FetchBalances(ctx, client, CombineUnique(nodekeys, votekeys)) + fetchedBalances, err := FetchBalances(ctx, client, CombineUnique(simulator.Nodekeys, simulator.Votekeys)) assert.NoError(t, err) - assert.Equal(t, balances, fetchedBalances) + assert.Equal(t, + map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3, "AAA": 4, "BBB": 5, "CCC": 6}, + fetchedBalances, + ) } func TestGetAssociatedVoteAccounts(t *testing.T) { - _, client := rpc.NewMockClient(t, - map[string]any{"getVoteAccounts": rawVoteAccounts}, - nil, - nil, - nil, - nil, - ) + simulator, client := NewSimulator(t, 1) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - voteAccounts, err := GetAssociatedVoteAccounts(ctx, client, rpc.CommitmentFinalized, nodekeys) + voteAccounts, err := GetAssociatedVoteAccounts(ctx, client, rpc.CommitmentFinalized, simulator.Nodekeys) assert.NoError(t, err) - assert.Equal(t, votekeys, voteAccounts) + assert.Equal(t, simulator.Votekeys, voteAccounts) } func TestGetEpochBounds(t *testing.T) { diff --git a/pkg/rpc/mock.go b/pkg/rpc/mock.go index a79fe94..085543c 100644 --- a/pkg/rpc/mock.go +++ b/pkg/rpc/mock.go @@ -35,7 +35,7 @@ type ( inflationRewards map[string]int easyResults map[string]any - slotInfos map[int]MockSlotInfo + SlotInfos map[int]MockSlotInfo validatorInfos map[string]MockValidatorInfo } @@ -76,7 +76,7 @@ func NewMockServer( easyResults: easyResults, balances: balances, inflationRewards: inflationRewards, - slotInfos: slotInfos, + SlotInfos: slotInfos, validatorInfos: validatorInfos, } @@ -130,10 +130,10 @@ func (s *MockServer) SetOpt(opt MockOpt, key any, value any) { } s.easyResults[key.(string)] = value case SlotInfosOpt: - if s.slotInfos == nil { - s.slotInfos = make(map[int]MockSlotInfo) + if s.SlotInfos == nil { + s.SlotInfos = make(map[int]MockSlotInfo) } - s.slotInfos[key.(int)] = value.(MockSlotInfo) + s.SlotInfos[key.(int)] = value.(MockSlotInfo) case ValidatorInfoOpt: if s.validatorInfos == nil { s.validatorInfos = make(map[string]MockValidatorInfo) @@ -174,13 +174,13 @@ func (s *MockServer) getResult(method string, params ...any) (any, *RPCError) { return rewards, nil } - if method == "getBlock" && s.slotInfos != nil { + if method == "getBlock" && s.SlotInfos != nil { // get params: slot := int(params[0].(float64)) config := params[1].(map[string]any) transactionDetails, rewardsIncluded := config["transactionDetails"].(string), config["rewards"].(bool) - slotInfo, ok := s.slotInfos[slot] + slotInfo, ok := s.SlotInfos[slot] if !ok { s.logger.Warnf("no slot info for slot %d", slot) return nil, &RPCError{Code: BlockCleanedUpCode, Message: "Block cleaned up."} @@ -211,7 +211,7 @@ func (s *MockServer) getResult(method string, params ...any) (any, *RPCError) { return map[string]any{"rewards": rewards, "transactions": transactions}, nil } - if method == "getBlockProduction" && s.slotInfos != nil { + if method == "getBlockProduction" && s.SlotInfos != nil { // get params: config := params[0].(map[string]any) slotRange := config["range"].(map[string]any) @@ -222,7 +222,7 @@ func (s *MockServer) getResult(method string, params ...any) (any, *RPCError) { byIdentity[nodekey] = []int{0, 0} } for i := firstSlot; i <= lastSlot; i++ { - info := s.slotInfos[i] + info := s.SlotInfos[i] production := byIdentity[info.Leader] production[0]++ if info.Block != nil { diff --git a/pkg/slog/logger.go b/pkg/slog/logger.go index 01089c2..0f4a6d3 100644 --- a/pkg/slog/logger.go +++ b/pkg/slog/logger.go @@ -49,6 +49,10 @@ func getEnvLogLevel() zapcore.Level { return zapcore.WarnLevel case "error": return zapcore.ErrorLevel + case "panic": + return zapcore.PanicLevel + case "fatal": + return zapcore.FatalLevel default: fmt.Printf("Unrecognised 'LOG_LEVEL' environment variable '%s', using 'info'\n", level) return zapcore.InfoLevel