2024-06-15 03:04:07 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2024-10-31 13:35:14 -07:00
|
|
|
"github.com/asymmetric-research/solana-exporter/pkg/rpc"
|
2024-06-15 03:04:07 -07:00
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
|
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
|
|
|
"github.com/stretchr/testify/assert"
|
2024-10-28 15:38:48 -07:00
|
|
|
"regexp"
|
2024-06-15 03:04:07 -07:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type slotMetricValues struct {
|
|
|
|
SlotHeight float64
|
|
|
|
TotalTransactions float64
|
|
|
|
EpochNumber float64
|
|
|
|
EpochFirstSlot float64
|
|
|
|
EpochLastSlot float64
|
2024-10-28 15:15:21 -07:00
|
|
|
BlockHeight float64
|
2024-06-15 03:04:07 -07:00
|
|
|
}
|
|
|
|
|
2024-10-28 15:38:48 -07:00
|
|
|
// extractName takes a Prometheus descriptor and returns its name
|
|
|
|
func extractName(desc *prometheus.Desc) string {
|
|
|
|
// Get the string representation of the descriptor
|
|
|
|
descString := desc.String()
|
|
|
|
// Use regex to extract the metric name and help message from the descriptor string
|
|
|
|
reName := regexp.MustCompile(`fqName: "([^"]+)"`)
|
|
|
|
nameMatch := reName.FindStringSubmatch(descString)
|
|
|
|
|
|
|
|
var name string
|
|
|
|
if len(nameMatch) > 1 {
|
|
|
|
name = nameMatch[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2024-10-15 03:30:53 -07:00
|
|
|
func getSlotMetricValues(watcher *SlotWatcher) slotMetricValues {
|
2024-06-15 03:04:07 -07:00
|
|
|
return slotMetricValues{
|
2024-10-15 03:30:53 -07:00
|
|
|
SlotHeight: testutil.ToFloat64(watcher.SlotHeightMetric),
|
|
|
|
TotalTransactions: testutil.ToFloat64(watcher.TotalTransactionsMetric),
|
|
|
|
EpochNumber: testutil.ToFloat64(watcher.EpochNumberMetric),
|
|
|
|
EpochFirstSlot: testutil.ToFloat64(watcher.EpochFirstSlotMetric),
|
|
|
|
EpochLastSlot: testutil.ToFloat64(watcher.EpochLastSlotMetric),
|
2024-10-28 15:15:21 -07:00
|
|
|
BlockHeight: testutil.ToFloat64(watcher.BlockHeightMetric),
|
2024-06-15 03:04:07 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func assertSlotMetricsChangeCorrectly(t *testing.T, initial slotMetricValues, final slotMetricValues) {
|
|
|
|
// make sure that things have increased
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.Greaterf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
final.SlotHeight,
|
|
|
|
initial.SlotHeight,
|
|
|
|
"Slot has not increased! (%v -> %v)",
|
2024-10-28 15:15:21 -07:00
|
|
|
initial.SlotHeight, final.SlotHeight,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.Greaterf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
final.TotalTransactions,
|
|
|
|
initial.TotalTransactions,
|
|
|
|
"Total transactions have not increased! (%v -> %v)",
|
2024-10-28 15:15:21 -07:00
|
|
|
initial.TotalTransactions, final.TotalTransactions,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.GreaterOrEqualf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
final.EpochNumber,
|
|
|
|
initial.EpochNumber,
|
|
|
|
"Epoch number has decreased! (%v -> %v)",
|
2024-10-28 15:15:21 -07:00
|
|
|
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,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-10-28 09:19:07 -07:00
|
|
|
func TestSlotWatcher_WatchSlots_Static(t *testing.T) {
|
2024-10-28 15:15:21 -07:00
|
|
|
// TODO: is this test necessary? If not - remove, else, could definitely do with a clean.
|
2024-10-28 09:19:07 -07:00
|
|
|
|
2024-10-28 15:38:48 -07:00
|
|
|
ctx := context.Background()
|
2024-10-28 09:19:07 -07:00
|
|
|
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator, client := NewSimulator(t, 35)
|
|
|
|
watcher := NewSlotWatcher(client, newTestConfig(simulator, true))
|
2024-10-15 03:30:53 -07:00
|
|
|
// reset metrics before running tests:
|
2024-10-24 03:09:32 -07:00
|
|
|
watcher.LeaderSlotsMetric.Reset()
|
2024-10-15 03:30:53 -07:00
|
|
|
watcher.LeaderSlotsByEpochMetric.Reset()
|
|
|
|
|
2024-10-25 04:45:40 -07:00
|
|
|
go watcher.WatchSlots(ctx)
|
2024-10-07 03:40:34 -07:00
|
|
|
|
|
|
|
// make sure inflation rewards are collected:
|
2024-10-28 09:19:07 -07:00
|
|
|
epochInfo, err := client.GetEpochInfo(ctx, rpc.CommitmentFinalized)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
err = watcher.fetchAndEmitInflationRewards(ctx, epochInfo.Epoch)
|
2024-10-07 03:40:34 -07:00
|
|
|
assert.NoError(t, err)
|
2024-06-15 03:04:07 -07:00
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
|
2024-10-07 03:40:34 -07:00
|
|
|
type testCase struct {
|
2024-10-30 00:29:32 -07:00
|
|
|
name string
|
2024-06-15 03:04:07 -07:00
|
|
|
expectedValue float64
|
2024-10-30 00:29:32 -07:00
|
|
|
metric prometheus.Collector
|
2024-10-07 03:40:34 -07:00
|
|
|
}
|
2024-10-28 09:19:07 -07:00
|
|
|
|
|
|
|
// epoch info tests:
|
|
|
|
firstSlot, lastSlot := GetEpochBounds(epochInfo)
|
2024-10-07 03:40:34 -07:00
|
|
|
tests := []testCase{
|
2024-10-30 00:29:32 -07:00
|
|
|
{"slot_height", float64(epochInfo.AbsoluteSlot), watcher.SlotHeightMetric},
|
|
|
|
{"total_transactions", float64(epochInfo.TransactionCount), watcher.TotalTransactionsMetric},
|
|
|
|
{"epoch_number", float64(epochInfo.Epoch), watcher.EpochNumberMetric},
|
|
|
|
{"epoch_first_slot", float64(firstSlot), watcher.EpochFirstSlotMetric},
|
|
|
|
{"epoch_last_slot", float64(lastSlot), watcher.EpochLastSlotMetric},
|
2024-06-15 03:04:07 -07:00
|
|
|
}
|
|
|
|
|
2024-10-07 03:40:34 -07:00
|
|
|
// add inflation reward tests:
|
2024-10-28 15:15:21 -07:00
|
|
|
inflationRewards, err := client.GetInflationReward(ctx, rpc.CommitmentFinalized, simulator.Votekeys, 2)
|
2024-10-28 09:19:07 -07:00
|
|
|
assert.NoError(t, err)
|
|
|
|
for i, rewardInfo := range inflationRewards {
|
|
|
|
tests = append(
|
|
|
|
tests,
|
|
|
|
testCase{
|
2024-10-30 00:29:32 -07:00
|
|
|
fmt.Sprintf("inflation_rewards_%s", simulator.Votekeys[i]),
|
|
|
|
float64(rewardInfo.Amount) / float64(rpc.LamportsInSol),
|
|
|
|
watcher.InflationRewardsMetric.WithLabelValues(simulator.Votekeys[i], toString(epochInfo.Epoch)),
|
2024-10-28 09:19:07 -07:00
|
|
|
},
|
|
|
|
)
|
2024-10-07 03:40:34 -07:00
|
|
|
}
|
|
|
|
|
2024-10-30 00:29:32 -07:00
|
|
|
for _, test := range tests {
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
assert.Equal(t, test.expectedValue, testutil.ToFloat64(test.metric))
|
2024-06-15 03:04:07 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-10-28 09:19:07 -07:00
|
|
|
func TestSlotWatcher_WatchSlots_Dynamic(t *testing.T) {
|
2024-10-28 15:15:21 -07:00
|
|
|
// 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
|
|
|
|
|
2024-06-15 03:04:07 -07:00
|
|
|
// create clients:
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator, client := NewSimulator(t, 23)
|
|
|
|
watcher := NewSlotWatcher(client, newTestConfig(simulator, true))
|
2024-10-15 03:30:53 -07:00
|
|
|
// reset metrics before running tests:
|
2024-10-24 03:09:32 -07:00
|
|
|
watcher.LeaderSlotsMetric.Reset()
|
2024-10-15 03:30:53 -07:00
|
|
|
watcher.LeaderSlotsByEpochMetric.Reset()
|
2024-06-15 03:04:07 -07:00
|
|
|
|
|
|
|
// start client/collector and wait a bit:
|
2024-10-28 15:15:21 -07:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
go watcher.WatchSlots(ctx)
|
2024-06-15 03:04:07 -07:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
2024-10-28 15:15:21 -07:00
|
|
|
go simulator.Run(ctx)
|
2024-06-15 03:04:07 -07:00
|
|
|
time.Sleep(time.Second)
|
|
|
|
|
2024-10-15 03:30:53 -07:00
|
|
|
initial := getSlotMetricValues(watcher)
|
2024-06-15 03:04:07 -07:00
|
|
|
|
|
|
|
// wait a bit:
|
|
|
|
var epochChanged bool
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
// wait a bit then get new metrics
|
|
|
|
time.Sleep(time.Second)
|
2024-10-15 03:30:53 -07:00
|
|
|
final := getSlotMetricValues(watcher)
|
2024-06-15 03:04:07 -07:00
|
|
|
|
|
|
|
// make sure things are changing correctly:
|
|
|
|
assertSlotMetricsChangeCorrectly(t, initial, final)
|
|
|
|
|
|
|
|
// sense check to make sure the exporter is not "ahead" of the client (due to double counting or whatever)
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.LessOrEqualf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
int(final.SlotHeight),
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator.Slot,
|
|
|
|
"Exporter slot (%v) ahead of simulator slot (%v)!",
|
|
|
|
int(final.SlotHeight), simulator.Slot,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.LessOrEqualf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
int(final.TotalTransactions),
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator.TransactionCount,
|
|
|
|
"Exporter transaction count (%v) ahead of simulator transaction count (%v)!",
|
|
|
|
int(final.TotalTransactions), simulator.TransactionCount,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
2024-10-28 15:15:21 -07:00
|
|
|
assert.LessOrEqualf(t,
|
2024-06-15 03:04:07 -07:00
|
|
|
int(final.EpochNumber),
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator.Epoch,
|
|
|
|
"Exporter epoch (%v) ahead of simulator epoch (%v)!",
|
|
|
|
int(final.EpochNumber), simulator.Epoch,
|
2024-06-15 03:04:07 -07:00
|
|
|
)
|
|
|
|
|
2024-10-28 15:15:21 -07:00
|
|
|
// 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,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-06-15 03:04:07 -07:00
|
|
|
// check if epoch changed
|
|
|
|
if final.EpochNumber > initial.EpochNumber {
|
|
|
|
epochChanged = true
|
2024-10-28 15:15:21 -07:00
|
|
|
|
|
|
|
// 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,
|
2024-10-29 03:51:45 -07:00
|
|
|
float64(simulator.InflationRewardLamports)/rpc.LamportsInSol,
|
2024-10-28 15:15:21 -07:00
|
|
|
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,
|
2024-10-29 03:51:45 -07:00
|
|
|
float64(simulator.FeeRewardLamports*leaderSlotsPerEpoch*3/4)/rpc.LamportsInSol,
|
2024-10-28 15:15:21 -07:00
|
|
|
testutil.ToFloat64(
|
|
|
|
watcher.FeeRewardsMetric.WithLabelValues(nodekey, toString(initial.EpochNumber)),
|
|
|
|
),
|
|
|
|
"Incorrect fee reward for %s at epoch %v",
|
|
|
|
nodekey, initial.EpochNumber,
|
|
|
|
)
|
|
|
|
}
|
2024-06-15 03:04:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// make current final the new initial (for next iteration)
|
|
|
|
initial = final
|
|
|
|
}
|
|
|
|
|
|
|
|
// epoch should have changed somewhere
|
|
|
|
assert.Truef(t, epochChanged, "Epoch has not changed!")
|
|
|
|
}
|