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"
|
2024-10-31 13:35:14 -07:00
|
|
|
"github.com/asymmetric-research/solana-exporter/pkg/rpc"
|
2024-06-12 14:49:16 -07:00
|
|
|
"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 {
|
2024-06-13 15:53:19 -07:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return
|
|
|
|
|
|
|
|
default:
|
2024-10-28 09:19:07 -07:00
|
|
|
c.Slot++
|
|
|
|
c.PopulateSlot(c.Slot)
|
2024-06-13 15:53:19 -07:00
|
|
|
// 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 {
|
2024-10-25 04:45:40 -07:00
|
|
|
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(),
|
2024-10-25 04:45:40 -07:00
|
|
|
":8080",
|
2024-10-28 15:15:21 -07:00
|
|
|
simulator.Nodekeys,
|
|
|
|
simulator.Votekeys,
|
2024-10-25 04:45:40 -07:00
|
|
|
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))
|
2024-06-15 03:04:07 -07:00
|
|
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
|
|
|
|
2024-10-30 02:31:53 -07:00
|
|
|
stake := float64(1_000_000) / rpc.LamportsInSol
|
|
|
|
|
2024-06-15 03:04:07 -07:00
|
|
|
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-06-15 03:04:07 -07:00
|
|
|
}
|
|
|
|
|
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)
|
|
|
|
})
|
|
|
|
}
|
2024-06-15 03:04:07 -07:00
|
|
|
}
|