added dynamic unit tests
This commit is contained in:
parent
f5d604d4f1
commit
b742be47ef
|
@ -0,0 +1,212 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSolanaCollector_Collect_Dynamic(t *testing.T) {
|
||||
client := newDynamicRPCClient()
|
||||
collector := createSolanaCollector(
|
||||
client,
|
||||
slotPacerSchedule,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
// start off by testing initial state:
|
||||
testCases := []collectionTest{
|
||||
{
|
||||
Name: "solana_active_validators",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_active_validators Total number of active validators by state
|
||||
# TYPE solana_active_validators gauge
|
||||
solana_active_validators{state="current"} 3
|
||||
solana_active_validators{state="delinquent"} 0
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_activated_stake",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_activated_stake Activated stake per validator
|
||||
# TYPE solana_validator_activated_stake gauge
|
||||
solana_validator_activated_stake{nodekey="aaa",pubkey="AAA"} 1000000
|
||||
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 1000000
|
||||
solana_validator_activated_stake{nodekey="ccc",pubkey="CCC"} 1000000
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_root_slot",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_root_slot Root slot per validator
|
||||
# TYPE solana_validator_root_slot gauge
|
||||
solana_validator_root_slot{nodekey="aaa",pubkey="AAA"} 0
|
||||
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 0
|
||||
solana_validator_root_slot{nodekey="ccc",pubkey="CCC"} 0
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_delinquent",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_delinquent Whether a validator is delinquent
|
||||
# TYPE solana_validator_delinquent gauge
|
||||
solana_validator_delinquent{nodekey="aaa",pubkey="AAA"} 0
|
||||
solana_validator_delinquent{nodekey="bbb",pubkey="BBB"} 0
|
||||
solana_validator_delinquent{nodekey="ccc",pubkey="CCC"} 0
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_node_version",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_node_version Node version of solana
|
||||
# TYPE solana_node_version gauge
|
||||
solana_node_version{version="v1.0.0"} 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
runCollectionTests(t, collector, testCases)
|
||||
|
||||
// now make some changes:
|
||||
client.UpdateStake("aaa", 2_000_000)
|
||||
client.UpdateStake("bbb", 500_000)
|
||||
client.UpdateDelinquency("ccc", true)
|
||||
client.UpdateVersion("v1.2.3")
|
||||
|
||||
// now test the final state
|
||||
testCases = []collectionTest{
|
||||
{
|
||||
Name: "solana_active_validators",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_active_validators Total number of active validators by state
|
||||
# TYPE solana_active_validators gauge
|
||||
solana_active_validators{state="current"} 2
|
||||
solana_active_validators{state="delinquent"} 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_activated_stake",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_activated_stake Activated stake per validator
|
||||
# TYPE solana_validator_activated_stake gauge
|
||||
solana_validator_activated_stake{nodekey="aaa",pubkey="AAA"} 2000000
|
||||
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 500000
|
||||
solana_validator_activated_stake{nodekey="ccc",pubkey="CCC"} 1000000
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_root_slot",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_root_slot Root slot per validator
|
||||
# TYPE solana_validator_root_slot gauge
|
||||
solana_validator_root_slot{nodekey="aaa",pubkey="AAA"} 0
|
||||
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 0
|
||||
solana_validator_root_slot{nodekey="ccc",pubkey="CCC"} 0
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_delinquent",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_delinquent Whether a validator is delinquent
|
||||
# TYPE solana_validator_delinquent gauge
|
||||
solana_validator_delinquent{nodekey="aaa",pubkey="AAA"} 0
|
||||
solana_validator_delinquent{nodekey="bbb",pubkey="BBB"} 0
|
||||
solana_validator_delinquent{nodekey="ccc",pubkey="CCC"} 1
|
||||
`,
|
||||
},
|
||||
{
|
||||
Name: "solana_node_version",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_node_version Node version of solana
|
||||
# TYPE solana_node_version gauge
|
||||
solana_node_version{version="v1.2.3"} 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
runCollectionTests(t, collector, testCases)
|
||||
}
|
||||
|
||||
type slotMetricValues struct {
|
||||
SlotHeight float64
|
||||
TotalTransactions float64
|
||||
EpochNumber float64
|
||||
EpochFirstSlot float64
|
||||
EpochLastSlot float64
|
||||
}
|
||||
|
||||
func getSlotMetricValues() slotMetricValues {
|
||||
return slotMetricValues{
|
||||
SlotHeight: testutil.ToFloat64(confirmedSlotHeight),
|
||||
TotalTransactions: testutil.ToFloat64(totalTransactionsTotal),
|
||||
EpochNumber: testutil.ToFloat64(currentEpochNumber),
|
||||
EpochFirstSlot: testutil.ToFloat64(epochFirstSlot),
|
||||
EpochLastSlot: testutil.ToFloat64(epochLastSlot),
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) {
|
||||
// this test passes, however, it seems to cause the static tests to fail (after this test runs,
|
||||
// the static tests fail to set their correct values to the prometheus metrics). So, putting this
|
||||
// here while I debug
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
|
||||
// create clients:
|
||||
client := newDynamicRPCClient()
|
||||
collector := createSolanaCollector(
|
||||
client,
|
||||
300*time.Millisecond,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
// start client/collector and wait a bit:
|
||||
go client.Run()
|
||||
time.Sleep(1 * time.Second)
|
||||
go collector.WatchSlots()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
initial := getSlotMetricValues()
|
||||
|
||||
// wait a bit:
|
||||
var epochChanged bool
|
||||
for i := 0; i < 5; i++ {
|
||||
// wait a bit then get new metrics
|
||||
time.Sleep(1 * time.Second)
|
||||
final := getSlotMetricValues()
|
||||
|
||||
// make sure that things have increased
|
||||
assert.Greaterf(
|
||||
t,
|
||||
final.SlotHeight,
|
||||
initial.SlotHeight,
|
||||
"Slot has not increased! (%v -> %v)",
|
||||
initial.SlotHeight,
|
||||
final.SlotHeight,
|
||||
)
|
||||
assert.Greaterf(
|
||||
t,
|
||||
final.TotalTransactions,
|
||||
initial.TotalTransactions,
|
||||
"Total transactions have not increased! (%v -> %v)",
|
||||
initial.TotalTransactions,
|
||||
final.TotalTransactions,
|
||||
)
|
||||
assert.GreaterOrEqualf(
|
||||
t,
|
||||
final.EpochNumber,
|
||||
initial.EpochNumber,
|
||||
"Epoch number has decreased! (%v -> %v)",
|
||||
initial.EpochNumber,
|
||||
final.EpochNumber,
|
||||
)
|
||||
|
||||
// make current final the new initial (for next iteration)
|
||||
initial = final
|
||||
}
|
||||
|
||||
assert.True(t, epochChanged)
|
||||
}
|
|
@ -26,6 +26,7 @@ func init() {
|
|||
|
||||
type solanaCollector struct {
|
||||
rpcClient rpc.Provider
|
||||
slotPace time.Duration
|
||||
|
||||
totalValidatorsDesc *prometheus.Desc
|
||||
validatorActivatedStake *prometheus.Desc
|
||||
|
@ -35,9 +36,10 @@ type solanaCollector struct {
|
|||
solanaVersion *prometheus.Desc
|
||||
}
|
||||
|
||||
func createSolanaCollector(provider rpc.Provider) *solanaCollector {
|
||||
func createSolanaCollector(provider rpc.Provider, slotPace time.Duration) *solanaCollector {
|
||||
return &solanaCollector{
|
||||
rpcClient: provider,
|
||||
slotPace: slotPace,
|
||||
totalValidatorsDesc: prometheus.NewDesc(
|
||||
"solana_active_validators",
|
||||
"Total number of active validators by state",
|
||||
|
@ -66,7 +68,7 @@ func createSolanaCollector(provider rpc.Provider) *solanaCollector {
|
|||
}
|
||||
|
||||
func NewSolanaCollector(rpcAddr string) *solanaCollector {
|
||||
return createSolanaCollector(rpc.NewRPCClient(rpcAddr))
|
||||
return createSolanaCollector(rpc.NewRPCClient(rpcAddr), slotPacerSchedule)
|
||||
}
|
||||
|
||||
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||
|
@ -127,7 +129,7 @@ func (c *solanaCollector) Collect(ch chan<- prometheus.Metric) {
|
|||
if err != nil {
|
||||
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
|
||||
} else {
|
||||
ch <- prometheus.MustNewConstMetric(c.solanaVersion, prometheus.GaugeValue, 1, *version)
|
||||
ch <- prometheus.MustNewConstMetric(c.solanaVersion, prometheus.GaugeValue, 1, version)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ func (c *solanaCollector) WatchSlots() {
|
|||
if err != nil {
|
||||
klog.Error(err)
|
||||
}
|
||||
ticker := time.NewTicker(slotPacerSchedule)
|
||||
ticker := time.NewTicker(c.slotPace)
|
||||
|
||||
for {
|
||||
<-ticker.C
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
|
@ -10,71 +9,83 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var staticCollector = createSolanaCollector(&staticRPCClient{})
|
||||
func TestSolanaCollector_Collect_Static(t *testing.T) {
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
slotPacerSchedule,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
func TestSolanaCollector_Collect(t *testing.T) {
|
||||
prometheus.NewPedanticRegistry().MustRegister(staticCollector)
|
||||
|
||||
testCases := map[string]string{
|
||||
"solana_active_validators": `
|
||||
testCases := []collectionTest{
|
||||
{
|
||||
Name: "solana_active_validators",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_active_validators Total number of active validators by state
|
||||
# TYPE solana_active_validators gauge
|
||||
solana_active_validators{state="current"} 2
|
||||
solana_active_validators{state="delinquent"} 1
|
||||
`,
|
||||
"solana_validator_activated_stake": `
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_activated_stake",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_activated_stake Activated stake per validator
|
||||
# TYPE solana_validator_activated_stake gauge
|
||||
solana_validator_activated_stake{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 49
|
||||
solana_validator_activated_stake{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 42
|
||||
solana_validator_activated_stake{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 43
|
||||
solana_validator_activated_stake{nodekey="aaa",pubkey="AAA"} 49
|
||||
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 42
|
||||
solana_validator_activated_stake{nodekey="ccc",pubkey="CCC"} 43
|
||||
`,
|
||||
"solana_validator_last_vote": `
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_last_vote",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_last_vote Last voted slot per validator
|
||||
# TYPE solana_validator_last_vote gauge
|
||||
solana_validator_last_vote{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 92
|
||||
solana_validator_last_vote{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 147
|
||||
solana_validator_last_vote{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 148
|
||||
solana_validator_last_vote{nodekey="aaa",pubkey="AAA"} 92
|
||||
solana_validator_last_vote{nodekey="bbb",pubkey="BBB"} 147
|
||||
solana_validator_last_vote{nodekey="ccc",pubkey="CCC"} 148
|
||||
`,
|
||||
"solana_validator_root_slot": `
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_root_slot",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_root_slot Root slot per validator
|
||||
# TYPE solana_validator_root_slot gauge
|
||||
solana_validator_root_slot{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 3
|
||||
solana_validator_root_slot{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 18
|
||||
solana_validator_root_slot{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 19
|
||||
solana_validator_root_slot{nodekey="aaa",pubkey="AAA"} 3
|
||||
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 18
|
||||
solana_validator_root_slot{nodekey="ccc",pubkey="CCC"} 19
|
||||
`,
|
||||
"solana_validator_delinquent": `
|
||||
},
|
||||
{
|
||||
Name: "solana_validator_delinquent",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_validator_delinquent Whether a validator is delinquent
|
||||
# TYPE solana_validator_delinquent gauge
|
||||
solana_validator_delinquent{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 1
|
||||
solana_validator_delinquent{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 0
|
||||
solana_validator_delinquent{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 0
|
||||
solana_validator_delinquent{nodekey="aaa",pubkey="AAA"} 1
|
||||
solana_validator_delinquent{nodekey="bbb",pubkey="BBB"} 0
|
||||
solana_validator_delinquent{nodekey="ccc",pubkey="CCC"} 0
|
||||
`,
|
||||
"solana_node_version": `
|
||||
},
|
||||
{
|
||||
Name: "solana_node_version",
|
||||
ExpectedResponse: `
|
||||
# HELP solana_node_version Node version of solana
|
||||
# TYPE solana_node_version gauge
|
||||
solana_node_version{version="1.16.7"} 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for testName, expectedValue := range testCases {
|
||||
t.Run(
|
||||
testName,
|
||||
func(t *testing.T) {
|
||||
if err := testutil.CollectAndCompare(
|
||||
staticCollector,
|
||||
bytes.NewBufferString(expectedValue),
|
||||
testName,
|
||||
); err != nil {
|
||||
t.Errorf("unexpected collecting result for %s: \n%s", testName, err)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
runCollectionTests(t, collector, testCases)
|
||||
}
|
||||
|
||||
func TestSolanaCollector_WatchSlots(t *testing.T) {
|
||||
go staticCollector.WatchSlots()
|
||||
func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
100*time.Millisecond,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
go collector.WatchSlots()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
tests := []struct {
|
||||
|
@ -124,8 +135,8 @@ func TestSolanaCollector_WatchSlots(t *testing.T) {
|
|||
for _, status := range statuses {
|
||||
// sub subtest for each status (as each one requires a different calc)
|
||||
t.Run(status, func(t *testing.T) {
|
||||
for _, testValidator := range testValidators {
|
||||
testBlockProductionMetric(t, metric, testValidator.identity, status)
|
||||
for _, identity := range identities {
|
||||
testBlockProductionMetric(t, metric, identity, status)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -154,5 +165,11 @@ func testBlockProductionMetric(
|
|||
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch))
|
||||
}
|
||||
// now we can do the assertion:
|
||||
assert.Equal(t, expectedValue, testutil.ToFloat64(metric.WithLabelValues(labels...)))
|
||||
assert.Equalf(
|
||||
t,
|
||||
expectedValue,
|
||||
testutil.ToFloat64(metric.WithLabelValues(labels...)),
|
||||
"wrong value for block-production metric with labels: %s",
|
||||
labels,
|
||||
)
|
||||
}
|
|
@ -1,29 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"github.com/certusone/solana_exporter/pkg/rpc"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
staticRPCClient struct{}
|
||||
// TODO: create dynamicRPCClient + according tests!
|
||||
staticRPCClient struct{}
|
||||
dynamicRPCClient struct {
|
||||
Slot int
|
||||
BlockHeight int
|
||||
Epoch int
|
||||
EpochSize int
|
||||
SlotTime time.Duration
|
||||
TransactionCount int
|
||||
Version string
|
||||
SlotInfos map[int]slotInfo
|
||||
LeaderIndex int
|
||||
ValidatorInfos map[string]validatorInfo
|
||||
}
|
||||
slotInfo struct {
|
||||
leader string
|
||||
blockProduced bool
|
||||
}
|
||||
validatorInfo struct {
|
||||
Stake int
|
||||
LastVote int
|
||||
Commission int
|
||||
Delinquent bool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
testValidators = []struct {
|
||||
identity string
|
||||
vote string
|
||||
}{
|
||||
{"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"},
|
||||
{"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", "4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"},
|
||||
{"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae", "xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"},
|
||||
identities = []string{
|
||||
"aaa",
|
||||
"bbb",
|
||||
"ccc",
|
||||
}
|
||||
n = len(testValidators)
|
||||
identityVotes = map[string]string{
|
||||
"aaa": "AAA",
|
||||
"bbb": "BBB",
|
||||
"ccc": "CCC",
|
||||
}
|
||||
nv = len(identities)
|
||||
staticEpochInfo = rpc.EpochInfo{
|
||||
AbsoluteSlot: 166598,
|
||||
BlockHeight: 166500,
|
||||
|
@ -36,44 +62,21 @@ var (
|
|||
FirstSlot: 1000,
|
||||
LastSlot: 2000,
|
||||
Hosts: map[string]rpc.BlockProductionPerHost{
|
||||
"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": {
|
||||
"bbb": {
|
||||
LeaderSlots: 400,
|
||||
BlocksProduced: 360,
|
||||
},
|
||||
"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": {
|
||||
"ccc": {
|
||||
LeaderSlots: 300,
|
||||
BlocksProduced: 296,
|
||||
},
|
||||
"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae": {
|
||||
"aaa": {
|
||||
LeaderSlots: 300,
|
||||
BlocksProduced: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetEpochInfo(ctx context.Context, commitment rpc.Commitment) (*rpc.EpochInfo, error) {
|
||||
return &staticEpochInfo, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetSlot(ctx context.Context) (int64, error) {
|
||||
return staticEpochInfo.AbsoluteSlot, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetVersion(ctx context.Context) (*string, error) {
|
||||
version := "1.16.7"
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetVoteAccounts(
|
||||
ctx context.Context,
|
||||
params []interface{},
|
||||
) (*rpc.VoteAccounts, error) {
|
||||
voteAccounts := rpc.VoteAccounts{
|
||||
staticVoteAccounts = rpc.VoteAccounts{
|
||||
Current: []rpc.VoteAccount{
|
||||
{
|
||||
ActivatedStake: 42,
|
||||
|
@ -84,9 +87,9 @@ func (c *staticRPCClient) GetVoteAccounts(
|
|||
},
|
||||
EpochVoteAccount: true,
|
||||
LastVote: 147,
|
||||
NodePubkey: "B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",
|
||||
NodePubkey: "bbb",
|
||||
RootSlot: 18,
|
||||
VotePubkey: "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw",
|
||||
VotePubkey: "BBB",
|
||||
},
|
||||
{
|
||||
ActivatedStake: 43,
|
||||
|
@ -97,9 +100,9 @@ func (c *staticRPCClient) GetVoteAccounts(
|
|||
},
|
||||
EpochVoteAccount: true,
|
||||
LastVote: 148,
|
||||
NodePubkey: "C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",
|
||||
NodePubkey: "ccc",
|
||||
RootSlot: 19,
|
||||
VotePubkey: "4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw",
|
||||
VotePubkey: "CCC",
|
||||
},
|
||||
},
|
||||
Delinquent: []rpc.VoteAccount{
|
||||
|
@ -112,13 +115,40 @@ func (c *staticRPCClient) GetVoteAccounts(
|
|||
},
|
||||
EpochVoteAccount: true,
|
||||
LastVote: 92,
|
||||
NodePubkey: "4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",
|
||||
NodePubkey: "aaa",
|
||||
RootSlot: 3,
|
||||
VotePubkey: "xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU",
|
||||
VotePubkey: "AAA",
|
||||
},
|
||||
},
|
||||
}
|
||||
return &voteAccounts, nil
|
||||
)
|
||||
|
||||
/*
|
||||
===== STATIC CLIENT =====:
|
||||
*/
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetEpochInfo(ctx context.Context, commitment rpc.Commitment) (*rpc.EpochInfo, error) {
|
||||
return &staticEpochInfo, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetSlot(ctx context.Context) (int64, error) {
|
||||
return staticEpochInfo.AbsoluteSlot, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetVersion(ctx context.Context) (string, error) {
|
||||
version := "1.16.7"
|
||||
return version, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *staticRPCClient) GetVoteAccounts(
|
||||
ctx context.Context,
|
||||
params []interface{},
|
||||
) (*rpc.VoteAccounts, error) {
|
||||
return &staticVoteAccounts, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
|
@ -130,6 +160,183 @@ func (c *staticRPCClient) GetBlockProduction(
|
|||
return staticBlockProduction, nil
|
||||
}
|
||||
|
||||
/*
|
||||
===== DYNAMIC CLIENT =====:
|
||||
*/
|
||||
|
||||
func newDynamicRPCClient() *dynamicRPCClient {
|
||||
validatorInfos := make(map[string]validatorInfo)
|
||||
for identity := range identityVotes {
|
||||
validatorInfos[identity] = validatorInfo{
|
||||
Stake: 1_000_000,
|
||||
LastVote: 0,
|
||||
Commission: 5,
|
||||
Delinquent: false,
|
||||
}
|
||||
}
|
||||
return &dynamicRPCClient{
|
||||
Slot: 0,
|
||||
BlockHeight: 0,
|
||||
Epoch: 0,
|
||||
EpochSize: 20,
|
||||
SlotTime: 100 * time.Millisecond,
|
||||
TransactionCount: 0,
|
||||
Version: "v1.0.0",
|
||||
SlotInfos: map[int]slotInfo{},
|
||||
LeaderIndex: 0,
|
||||
ValidatorInfos: validatorInfos,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) Run() {
|
||||
for {
|
||||
c.newSlot()
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) newSlot() {
|
||||
c.Slot++
|
||||
|
||||
// leader changes every 4 slots
|
||||
if c.Slot%4 == 0 {
|
||||
c.LeaderIndex = (c.LeaderIndex + 1) % nv
|
||||
}
|
||||
|
||||
if c.Slot%c.EpochSize == 0 {
|
||||
c.Epoch++
|
||||
}
|
||||
|
||||
// assume 90% chance of block produced:
|
||||
blockProduced := rand.Intn(100) <= 90
|
||||
// add slot info:
|
||||
c.SlotInfos[c.Slot] = slotInfo{
|
||||
leader: identities[c.LeaderIndex],
|
||||
blockProduced: blockProduced,
|
||||
}
|
||||
|
||||
if blockProduced {
|
||||
c.BlockHeight++
|
||||
// only add some transactions if a block was produced
|
||||
c.TransactionCount += rand.Intn(10)
|
||||
// assume both other validators voted
|
||||
for i := 1; i < 3; i++ {
|
||||
otherValidatorIndex := (c.LeaderIndex + i) % nv
|
||||
identity := identities[otherValidatorIndex]
|
||||
info := c.ValidatorInfos[identity]
|
||||
info.LastVote = c.Slot
|
||||
c.ValidatorInfos[identity] = info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) UpdateVersion(version string) {
|
||||
c.Version = version
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) UpdateStake(validator string, amount int) {
|
||||
info := c.ValidatorInfos[validator]
|
||||
info.Stake = amount
|
||||
c.ValidatorInfos[validator] = info
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) UpdateCommission(validator string, newCommission int) {
|
||||
info := c.ValidatorInfos[validator]
|
||||
info.Commission = newCommission
|
||||
c.ValidatorInfos[validator] = info
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) UpdateDelinquency(validator string, newDelinquent bool) {
|
||||
info := c.ValidatorInfos[validator]
|
||||
info.Delinquent = newDelinquent
|
||||
c.ValidatorInfos[validator] = info
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetEpochInfo(ctx context.Context, commitment rpc.Commitment) (*rpc.EpochInfo, error) {
|
||||
return &rpc.EpochInfo{
|
||||
AbsoluteSlot: int64(c.Slot),
|
||||
BlockHeight: int64(c.BlockHeight),
|
||||
Epoch: int64(c.Epoch),
|
||||
SlotIndex: int64(c.Slot % c.EpochSize),
|
||||
SlotsInEpoch: int64(c.EpochSize),
|
||||
TransactionCount: int64(c.TransactionCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetSlot(ctx context.Context) (int64, error) {
|
||||
return int64(c.Slot), nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetVersion(ctx context.Context) (string, error) {
|
||||
return c.Version, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetVoteAccounts(
|
||||
ctx context.Context,
|
||||
params []interface{},
|
||||
) (*rpc.VoteAccounts, error) {
|
||||
var currentVoteAccounts, delinquentVoteAccounts []rpc.VoteAccount
|
||||
for identity, vote := range identityVotes {
|
||||
info := c.ValidatorInfos[identity]
|
||||
voteAccount := rpc.VoteAccount{
|
||||
ActivatedStake: int64(info.Stake),
|
||||
Commission: info.Commission,
|
||||
EpochCredits: [][]int{},
|
||||
EpochVoteAccount: true,
|
||||
LastVote: info.LastVote,
|
||||
NodePubkey: identity,
|
||||
RootSlot: 0,
|
||||
VotePubkey: vote,
|
||||
}
|
||||
if info.Delinquent {
|
||||
delinquentVoteAccounts = append(delinquentVoteAccounts, voteAccount)
|
||||
} else {
|
||||
currentVoteAccounts = append(currentVoteAccounts, voteAccount)
|
||||
}
|
||||
}
|
||||
return &rpc.VoteAccounts{
|
||||
Current: currentVoteAccounts,
|
||||
Delinquent: delinquentVoteAccounts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetBlockProduction(
|
||||
ctx context.Context,
|
||||
firstSlot *int64,
|
||||
lastSlot *int64,
|
||||
) (rpc.BlockProduction, error) {
|
||||
hostProduction := make(map[string]rpc.BlockProductionPerHost)
|
||||
for _, identity := range identities {
|
||||
hostProduction[identity] = rpc.BlockProductionPerHost{LeaderSlots: 0, BlocksProduced: 0}
|
||||
}
|
||||
for i := *firstSlot; i <= *lastSlot; i++ {
|
||||
info := c.SlotInfos[int(i)]
|
||||
hp := hostProduction[info.leader]
|
||||
hp.LeaderSlots++
|
||||
if info.blockProduced {
|
||||
hp.BlocksProduced++
|
||||
}
|
||||
hostProduction[info.leader] = hp
|
||||
}
|
||||
return rpc.BlockProduction{
|
||||
FirstSlot: *firstSlot,
|
||||
LastSlot: *lastSlot,
|
||||
Hosts: hostProduction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
/*
|
||||
===== OTHER TEST UTILITIES =====:
|
||||
*/
|
||||
|
||||
// extractName takes a Prometheus descriptor and returns its name
|
||||
func extractName(desc *prometheus.Desc) string {
|
||||
// Get the string representation of the descriptor
|
||||
|
@ -148,110 +355,24 @@ func extractName(desc *prometheus.Desc) string {
|
|||
return name
|
||||
}
|
||||
|
||||
type (
|
||||
slotInfo struct {
|
||||
leader string
|
||||
blockProduced bool
|
||||
votes []string
|
||||
}
|
||||
dynamicRPCClient struct {
|
||||
slot int
|
||||
blockHeight int
|
||||
epoch int
|
||||
epochSize int
|
||||
transactionCount int
|
||||
version string
|
||||
slotsInfo map[int]slotInfo
|
||||
leaderIndex int
|
||||
}
|
||||
)
|
||||
|
||||
func (c *dynamicRPCClient) run() {
|
||||
ticker := time.NewTicker(100 * time.Millisecond)
|
||||
for {
|
||||
<-ticker.C
|
||||
|
||||
c.newSlot()
|
||||
}
|
||||
type collectionTest struct {
|
||||
Name string
|
||||
ExpectedResponse string
|
||||
}
|
||||
|
||||
func (c *dynamicRPCClient) newSlot() {
|
||||
c.slot++
|
||||
|
||||
// leader changes every 4 slots
|
||||
if c.slot%4 == 0 {
|
||||
c.leaderIndex = (c.leaderIndex + 1) % n
|
||||
}
|
||||
|
||||
if c.slot%c.epochSize == 0 {
|
||||
c.epoch++
|
||||
}
|
||||
|
||||
// assume 90% chance of block produced:
|
||||
blockProduced := rand.Intn(100) > 90
|
||||
if blockProduced {
|
||||
c.blockHeight++
|
||||
// only add some transactions if a block was produced
|
||||
c.transactionCount += rand.Intn(10)
|
||||
}
|
||||
|
||||
// add slot info:
|
||||
c.slotsInfo[c.slot] = slotInfo{
|
||||
leader: testValidators[c.leaderIndex].identity,
|
||||
blockProduced: blockProduced,
|
||||
// assume the other 2 validators voted:
|
||||
votes: []string{
|
||||
testValidators[(c.leaderIndex+1)%n].identity,
|
||||
testValidators[(c.leaderIndex+2)%n].identity,
|
||||
},
|
||||
func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases []collectionTest) {
|
||||
for _, test := range testCases {
|
||||
t.Run(
|
||||
test.Name,
|
||||
func(t *testing.T) {
|
||||
if err := testutil.CollectAndCompare(
|
||||
collector,
|
||||
bytes.NewBufferString(test.ExpectedResponse),
|
||||
test.Name,
|
||||
); err != nil {
|
||||
t.Errorf("unexpected collecting result for %s: \n%s", test.Name, err)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetEpochInfo(ctx context.Context, commitment rpc.Commitment) (*rpc.EpochInfo, error) {
|
||||
return &rpc.EpochInfo{
|
||||
AbsoluteSlot: int64(c.slot),
|
||||
BlockHeight: int64(c.blockHeight),
|
||||
Epoch: int64(c.epoch),
|
||||
SlotIndex: int64(c.slot % c.epochSize),
|
||||
SlotsInEpoch: int64(c.epochSize),
|
||||
TransactionCount: int64(c.transactionCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetSlot(ctx context.Context) (int64, error) {
|
||||
return int64(c.slot), nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetVersion(ctx context.Context) (*string, error) {
|
||||
return &c.version, nil
|
||||
}
|
||||
|
||||
//goland:noinspection GoUnusedParameter
|
||||
func (c *dynamicRPCClient) GetBlockProduction(
|
||||
ctx context.Context,
|
||||
firstSlot *int64,
|
||||
lastSlot *int64,
|
||||
) (rpc.BlockProduction, error) {
|
||||
hostProduction := map[string]rpc.BlockProductionPerHost{
|
||||
testValidators[0].identity: {0, 0},
|
||||
testValidators[1].identity: {0, 0},
|
||||
testValidators[2].identity: {0, 0},
|
||||
}
|
||||
for i := *firstSlot; i <= *lastSlot; i++ {
|
||||
slotInfo := c.slotsInfo[int(i)]
|
||||
hp := hostProduction[slotInfo.leader]
|
||||
hp.LeaderSlots++
|
||||
if slotInfo.blockProduced {
|
||||
hp.BlocksProduced++
|
||||
}
|
||||
hostProduction[slotInfo.leader] = hp
|
||||
}
|
||||
return rpc.BlockProduction{
|
||||
FirstSlot: *firstSlot,
|
||||
LastSlot: *lastSlot,
|
||||
Hosts: hostProduction,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ type Provider interface {
|
|||
// GetVersion retrieves the version of the Solana node.
|
||||
// The method takes a context for cancellation.
|
||||
// It returns a pointer to a string containing the version information, or an error if the operation fails.
|
||||
GetVersion(ctx context.Context) (*string, error)
|
||||
GetVersion(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
func (c Commitment) MarshalJSON() ([]byte, error) {
|
||||
|
|
|
@ -17,27 +17,27 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
func (c *Client) GetVersion(ctx context.Context) (*string, error) {
|
||||
func (c *Client) GetVersion(ctx context.Context) (string, error) {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getVersion", []interface{}{}))
|
||||
|
||||
if body == nil {
|
||||
return nil, fmt.Errorf("RPC call failed: Body empty")
|
||||
return "", fmt.Errorf("RPC call failed: Body empty")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RPC call failed: %w", err)
|
||||
return "", fmt.Errorf("RPC call failed: %w", err)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("version response: %v", string(body))
|
||||
|
||||
var resp GetVersionResponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response body: %w", err)
|
||||
return "", fmt.Errorf("failed to decode response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.Error.Code != 0 {
|
||||
return nil, fmt.Errorf("RPC error: %d %v", resp.Error.Code, resp.Error.Message)
|
||||
return "", fmt.Errorf("RPC error: %d %v", resp.Error.Code, resp.Error.Message)
|
||||
}
|
||||
|
||||
return &resp.Result.Version, nil
|
||||
return resp.Result.Version, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue