solana_exporter/cmd/solana-exporter/exporter_test.go

268 lines
6.5 KiB
Go
Raw Normal View History

2024-06-12 02:21:16 -07:00
package main
import (
2024-06-13 15:13:53 -07:00
"bytes"
2024-06-12 02:21:16 -07:00
"context"
2024-10-28 09:19:07 -07:00
"fmt"
"github.com/asymmetric-research/solana-exporter/pkg/rpc"
"github.com/prometheus/client_golang/prometheus"
2024-06-13 15:13:53 -07:00
"github.com/prometheus/client_golang/prometheus/testutil"
2024-06-14 12:31:45 -07:00
"github.com/stretchr/testify/assert"
2024-10-28 09:19:07 -07:00
"math"
2024-06-13 03:03:49 -07:00
"math/rand"
2024-10-28 09:19:07 -07:00
"slices"
"strings"
2024-06-13 15:13:53 -07:00
"testing"
2024-06-13 03:03:49 -07:00
"time"
2024-06-12 02:21:16 -07:00
)
type (
2024-10-28 09:54:51 -07:00
Simulator struct {
2024-10-28 09:19:07 -07:00
Server *rpc.MockServer
2024-06-13 15:13:53 -07:00
Slot int
BlockHeight int
Epoch int
TransactionCount int
2024-06-12 02:21:16 -07:00
2024-10-29 03:51:45 -07:00
// constants for the simulator
SlotTime time.Duration
EpochSize int
LeaderSchedule map[string][]int
Nodekeys []string
Votekeys []string
FeeRewardLamports int
InflationRewardLamports int
2024-10-28 09:19:07 -07:00
}
2024-06-13 15:13:53 -07:00
)
2024-10-28 09:54:51 -07:00
func NewSimulator(t *testing.T, slot int) (*Simulator, *rpc.Client) {
2024-10-28 15:15:21 -07:00
nodekeys := []string{"aaa", "bbb", "ccc"}
votekeys := []string{"AAA", "BBB", "CCC"}
2024-10-29 03:51:45 -07:00
feeRewardLamports, inflationRewardLamports := 10, 10
2024-10-28 15:15:21 -07:00
2024-10-28 09:19:07 -07:00
validatorInfos := make(map[string]rpc.MockValidatorInfo)
2024-10-28 15:15:21 -07:00
for i, nodekey := range nodekeys {
2024-10-28 09:19:07 -07:00
validatorInfos[nodekey] = rpc.MockValidatorInfo{
2024-10-28 15:15:21 -07:00
Votekey: votekeys[i],
2024-06-13 15:13:53 -07:00
Stake: 1_000_000,
Delinquent: false,
}
2024-06-13 03:03:49 -07:00
}
2024-10-28 15:15:21 -07:00
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},
}
2024-10-28 09:19:07 -07:00
mockServer, client := rpc.NewMockClient(t,
map[string]any{
"getVersion": map[string]string{"solana-core": "v1.0.0"},
2024-10-28 15:15:21 -07:00
"getLeaderSchedule": leaderSchedule,
2024-10-28 09:19:07 -07:00
"getHealth": "ok",
},
2024-10-28 15:15:21 -07:00
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{
2024-10-29 03:51:45 -07:00
"AAA": inflationRewardLamports,
"BBB": inflationRewardLamports,
"CCC": inflationRewardLamports,
2024-10-28 15:15:21 -07:00
},
2024-10-28 09:19:07 -07:00
nil,
validatorInfos,
)
2024-10-28 15:15:21 -07:00
simulator := Simulator{
2024-10-29 03:51:45 -07:00
Slot: 0,
Server: mockServer,
EpochSize: 24,
SlotTime: 100 * time.Millisecond,
LeaderSchedule: leaderSchedule,
Nodekeys: nodekeys,
Votekeys: votekeys,
InflationRewardLamports: inflationRewardLamports,
FeeRewardLamports: feeRewardLamports,
2024-06-13 03:03:49 -07:00
}
2024-10-28 15:15:21 -07:00
simulator.PopulateSlot(0)
if slot > 0 {
for {
simulator.Slot++
simulator.PopulateSlot(simulator.Slot)
if simulator.Slot == slot {
break
}
2024-10-28 09:19:07 -07:00
}
}
2024-10-28 15:15:21 -07:00
return &simulator, client
2024-06-13 15:13:53 -07:00
}
2024-06-13 03:03:49 -07:00
2024-10-28 09:54:51 -07:00
func (c *Simulator) Run(ctx context.Context) {
2024-06-13 03:03:49 -07:00
for {
select {
case <-ctx.Done():
return
default:
2024-10-28 09:19:07 -07:00
c.Slot++
c.PopulateSlot(c.Slot)
// add 5% noise to the slot time:
noiseRange := float64(c.SlotTime) * 0.05
noise := (rand.Float64()*2 - 1) * noiseRange
time.Sleep(c.SlotTime + time.Duration(noise))
}
2024-06-13 03:03:49 -07:00
}
}
2024-10-28 09:54:51 -07:00
func (c *Simulator) getLeader() string {
2024-10-28 09:19:07 -07:00
index := c.Slot % c.EpochSize
for leader, slots := range c.LeaderSchedule {
if slices.Contains(slots, index) {
return leader
2024-06-13 15:13:53 -07:00
}
2024-06-13 03:03:49 -07:00
}
2024-10-28 09:19:07 -07:00
panic(fmt.Sprintf("leader not found at slot %d", c.Slot))
2024-06-13 15:13:53 -07:00
}
2024-06-13 03:03:49 -07:00
2024-10-28 09:54:51 -07:00
func (c *Simulator) PopulateSlot(slot int) {
2024-10-28 09:19:07 -07:00
leader := c.getLeader()
2024-06-13 15:13:53 -07:00
2024-10-28 09:19:07 -07:00
var block *rpc.MockBlockInfo
// every 4th slot is skipped
if slot%4 != 3 {
c.BlockHeight++
// only add some transactions if a block was produced
transactions := [][]string{
{"aaa", "bbb", "ccc"},
{"xxx", "yyy", "zzz"},
2024-06-13 15:13:53 -07:00
}
2024-10-28 09:19:07 -07:00
// assume all validators voted
2024-10-28 15:15:21 -07:00
for _, nodekey := range c.Nodekeys {
2024-10-28 15:38:48 -07:00
transactions = append(transactions, []string{nodekey, strings.ToUpper(nodekey), VoteProgram})
2024-10-28 09:19:07 -07:00
info := c.Server.GetValidatorInfo(nodekey)
info.LastVote = slot
c.Server.SetOpt(rpc.ValidatorInfoOpt, nodekey, info)
2024-06-13 15:13:53 -07:00
}
2024-06-13 03:03:49 -07:00
2024-10-28 09:19:07 -07:00
c.TransactionCount += len(transactions)
2024-10-29 03:51:45 -07:00
block = &rpc.MockBlockInfo{Fee: c.FeeRewardLamports, Transactions: transactions}
2024-06-13 03:03:49 -07:00
}
2024-10-28 09:19:07 -07:00
// add slot info:
c.Server.SetOpt(rpc.SlotInfosOpt, slot, rpc.MockSlotInfo{Leader: leader, Block: block})
// now update the server:
c.Epoch = int(math.Floor(float64(slot) / float64(c.EpochSize)))
c.Server.SetOpt(
rpc.EasyResultsOpt,
"getSlot",
slot,
)
c.Server.SetOpt(
rpc.EasyResultsOpt,
"getEpochInfo",
map[string]int{
"absoluteSlot": slot,
"blockHeight": c.BlockHeight,
"epoch": c.Epoch,
"slotIndex": slot % c.EpochSize,
"slotsInEpoch": c.EpochSize,
"transactionCount": c.TransactionCount,
},
)
c.Server.SetOpt(
rpc.EasyResultsOpt,
"minimumLedgerSlot",
int(math.Max(0, float64(slot-c.EpochSize))),
)
c.Server.SetOpt(
rpc.EasyResultsOpt,
"getFirstAvailableBlock",
int(math.Max(0, float64(slot-c.EpochSize))),
)
2024-10-23 13:45:29 -07:00
}
2024-10-28 15:15:21 -07:00
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),
2024-10-28 15:15:21 -07:00
simulator.Server.URL(),
":8080",
2024-10-28 15:15:21 -07:00
simulator.Nodekeys,
simulator.Votekeys,
nil,
true,
true,
false,
pace,
}
return &config
}
2024-10-28 09:19:07 -07:00
func TestSolanaCollector(t *testing.T) {
2024-10-28 15:15:21 -07:00
simulator, client := NewSimulator(t, 35)
collector := NewSolanaCollector(client, newTestConfig(simulator, false))
prometheus.NewPedanticRegistry().MustRegister(collector)
2024-10-30 02:31:53 -07:00
stake := float64(1_000_000) / rpc.LamportsInSol
testCases := []collectionTest{
2024-10-28 15:15:21 -07:00
collector.ValidatorActiveStake.makeCollectionTest(
2024-10-30 02:31:53 -07:00
NewLV(stake, "aaa", "AAA"),
NewLV(stake, "bbb", "BBB"),
NewLV(stake, "ccc", "CCC"),
2024-10-28 15:15:21 -07:00
),
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),
),
}
2024-10-28 15:38:48 -07:00
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
err := testutil.CollectAndCompare(collector, bytes.NewBufferString(test.ExpectedResponse), test.Name)
assert.NoErrorf(t, err, "unexpected collecting result for %s: \n%s", test.Name, err)
})
}
}