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 {
|
type solanaCollector struct {
|
||||||
rpcClient rpc.Provider
|
rpcClient rpc.Provider
|
||||||
|
slotPace time.Duration
|
||||||
|
|
||||||
totalValidatorsDesc *prometheus.Desc
|
totalValidatorsDesc *prometheus.Desc
|
||||||
validatorActivatedStake *prometheus.Desc
|
validatorActivatedStake *prometheus.Desc
|
||||||
|
@ -35,9 +36,10 @@ type solanaCollector struct {
|
||||||
solanaVersion *prometheus.Desc
|
solanaVersion *prometheus.Desc
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSolanaCollector(provider rpc.Provider) *solanaCollector {
|
func createSolanaCollector(provider rpc.Provider, slotPace time.Duration) *solanaCollector {
|
||||||
return &solanaCollector{
|
return &solanaCollector{
|
||||||
rpcClient: provider,
|
rpcClient: provider,
|
||||||
|
slotPace: slotPace,
|
||||||
totalValidatorsDesc: prometheus.NewDesc(
|
totalValidatorsDesc: prometheus.NewDesc(
|
||||||
"solana_active_validators",
|
"solana_active_validators",
|
||||||
"Total number of active validators by state",
|
"Total number of active validators by state",
|
||||||
|
@ -66,7 +68,7 @@ func createSolanaCollector(provider rpc.Provider) *solanaCollector {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSolanaCollector(rpcAddr string) *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) {
|
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
@ -127,7 +129,7 @@ func (c *solanaCollector) Collect(ch chan<- prometheus.Metric) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
|
ch <- prometheus.NewInvalidMetric(c.solanaVersion, err)
|
||||||
} else {
|
} 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 {
|
if err != nil {
|
||||||
klog.Error(err)
|
klog.Error(err)
|
||||||
}
|
}
|
||||||
ticker := time.NewTicker(slotPacerSchedule)
|
ticker := time.NewTicker(c.slotPace)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
|
@ -10,71 +9,83 @@ import (
|
||||||
"time"
|
"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) {
|
testCases := []collectionTest{
|
||||||
prometheus.NewPedanticRegistry().MustRegister(staticCollector)
|
{
|
||||||
|
Name: "solana_active_validators",
|
||||||
testCases := map[string]string{
|
ExpectedResponse: `
|
||||||
"solana_active_validators": `
|
|
||||||
# HELP solana_active_validators Total number of active validators by state
|
# HELP solana_active_validators Total number of active validators by state
|
||||||
# TYPE solana_active_validators gauge
|
# TYPE solana_active_validators gauge
|
||||||
solana_active_validators{state="current"} 2
|
solana_active_validators{state="current"} 2
|
||||||
solana_active_validators{state="delinquent"} 1
|
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
|
# HELP solana_validator_activated_stake Activated stake per validator
|
||||||
# TYPE solana_validator_activated_stake gauge
|
# TYPE solana_validator_activated_stake gauge
|
||||||
solana_validator_activated_stake{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 49
|
solana_validator_activated_stake{nodekey="aaa",pubkey="AAA"} 49
|
||||||
solana_validator_activated_stake{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 42
|
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 42
|
||||||
solana_validator_activated_stake{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 43
|
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
|
# HELP solana_validator_last_vote Last voted slot per validator
|
||||||
# TYPE solana_validator_last_vote gauge
|
# TYPE solana_validator_last_vote gauge
|
||||||
solana_validator_last_vote{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 92
|
solana_validator_last_vote{nodekey="aaa",pubkey="AAA"} 92
|
||||||
solana_validator_last_vote{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 147
|
solana_validator_last_vote{nodekey="bbb",pubkey="BBB"} 147
|
||||||
solana_validator_last_vote{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 148
|
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
|
# HELP solana_validator_root_slot Root slot per validator
|
||||||
# TYPE solana_validator_root_slot gauge
|
# TYPE solana_validator_root_slot gauge
|
||||||
solana_validator_root_slot{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 3
|
solana_validator_root_slot{nodekey="aaa",pubkey="AAA"} 3
|
||||||
solana_validator_root_slot{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 18
|
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 18
|
||||||
solana_validator_root_slot{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 19
|
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
|
# HELP solana_validator_delinquent Whether a validator is delinquent
|
||||||
# TYPE solana_validator_delinquent gauge
|
# TYPE solana_validator_delinquent gauge
|
||||||
solana_validator_delinquent{nodekey="4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",pubkey="xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"} 1
|
solana_validator_delinquent{nodekey="aaa",pubkey="AAA"} 1
|
||||||
solana_validator_delinquent{nodekey="B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 0
|
solana_validator_delinquent{nodekey="bbb",pubkey="BBB"} 0
|
||||||
solana_validator_delinquent{nodekey="C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",pubkey="4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"} 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
|
# HELP solana_node_version Node version of solana
|
||||||
# TYPE solana_node_version gauge
|
# TYPE solana_node_version gauge
|
||||||
solana_node_version{version="1.16.7"} 1
|
solana_node_version{version="1.16.7"} 1
|
||||||
`,
|
`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for testName, expectedValue := range testCases {
|
runCollectionTests(t, collector, 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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSolanaCollector_WatchSlots(t *testing.T) {
|
func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||||
go staticCollector.WatchSlots()
|
collector := createSolanaCollector(
|
||||||
|
&staticRPCClient{},
|
||||||
|
100*time.Millisecond,
|
||||||
|
)
|
||||||
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||||
|
go collector.WatchSlots()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -124,8 +135,8 @@ func TestSolanaCollector_WatchSlots(t *testing.T) {
|
||||||
for _, status := range statuses {
|
for _, status := range statuses {
|
||||||
// sub subtest for each status (as each one requires a different calc)
|
// sub subtest for each status (as each one requires a different calc)
|
||||||
t.Run(status, func(t *testing.T) {
|
t.Run(status, func(t *testing.T) {
|
||||||
for _, testValidator := range testValidators {
|
for _, identity := range identities {
|
||||||
testBlockProductionMetric(t, metric, testValidator.identity, status)
|
testBlockProductionMetric(t, metric, identity, status)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -154,5 +165,11 @@ func testBlockProductionMetric(
|
||||||
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch))
|
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch))
|
||||||
}
|
}
|
||||||
// now we can do the assertion:
|
// 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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"github.com/certusone/solana_exporter/pkg/rpc"
|
"github.com/certusone/solana_exporter/pkg/rpc"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
staticRPCClient struct{}
|
staticRPCClient struct{}
|
||||||
// TODO: create dynamicRPCClient + according tests!
|
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 (
|
var (
|
||||||
testValidators = []struct {
|
identities = []string{
|
||||||
identity string
|
"aaa",
|
||||||
vote string
|
"bbb",
|
||||||
}{
|
"ccc",
|
||||||
{"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"},
|
|
||||||
{"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD", "4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw"},
|
|
||||||
{"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae", "xKUz6fZ79SXnjGYaYhhYTYQBoRUBoCyuDMkBa1tL3zU"},
|
|
||||||
}
|
}
|
||||||
n = len(testValidators)
|
identityVotes = map[string]string{
|
||||||
|
"aaa": "AAA",
|
||||||
|
"bbb": "BBB",
|
||||||
|
"ccc": "CCC",
|
||||||
|
}
|
||||||
|
nv = len(identities)
|
||||||
staticEpochInfo = rpc.EpochInfo{
|
staticEpochInfo = rpc.EpochInfo{
|
||||||
AbsoluteSlot: 166598,
|
AbsoluteSlot: 166598,
|
||||||
BlockHeight: 166500,
|
BlockHeight: 166500,
|
||||||
|
@ -36,44 +62,21 @@ var (
|
||||||
FirstSlot: 1000,
|
FirstSlot: 1000,
|
||||||
LastSlot: 2000,
|
LastSlot: 2000,
|
||||||
Hosts: map[string]rpc.BlockProductionPerHost{
|
Hosts: map[string]rpc.BlockProductionPerHost{
|
||||||
"B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": {
|
"bbb": {
|
||||||
LeaderSlots: 400,
|
LeaderSlots: 400,
|
||||||
BlocksProduced: 360,
|
BlocksProduced: 360,
|
||||||
},
|
},
|
||||||
"C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD": {
|
"ccc": {
|
||||||
LeaderSlots: 300,
|
LeaderSlots: 300,
|
||||||
BlocksProduced: 296,
|
BlocksProduced: 296,
|
||||||
},
|
},
|
||||||
"4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae": {
|
"aaa": {
|
||||||
LeaderSlots: 300,
|
LeaderSlots: 300,
|
||||||
BlocksProduced: 0,
|
BlocksProduced: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
staticVoteAccounts = rpc.VoteAccounts{
|
||||||
|
|
||||||
//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{
|
|
||||||
Current: []rpc.VoteAccount{
|
Current: []rpc.VoteAccount{
|
||||||
{
|
{
|
||||||
ActivatedStake: 42,
|
ActivatedStake: 42,
|
||||||
|
@ -84,9 +87,9 @@ func (c *staticRPCClient) GetVoteAccounts(
|
||||||
},
|
},
|
||||||
EpochVoteAccount: true,
|
EpochVoteAccount: true,
|
||||||
LastVote: 147,
|
LastVote: 147,
|
||||||
NodePubkey: "B97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",
|
NodePubkey: "bbb",
|
||||||
RootSlot: 18,
|
RootSlot: 18,
|
||||||
VotePubkey: "3ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw",
|
VotePubkey: "BBB",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ActivatedStake: 43,
|
ActivatedStake: 43,
|
||||||
|
@ -97,9 +100,9 @@ func (c *staticRPCClient) GetVoteAccounts(
|
||||||
},
|
},
|
||||||
EpochVoteAccount: true,
|
EpochVoteAccount: true,
|
||||||
LastVote: 148,
|
LastVote: 148,
|
||||||
NodePubkey: "C97CCUW3AEZFGy6uUg6zUdnNYvnVq5VG8PUtb2HayTDD",
|
NodePubkey: "ccc",
|
||||||
RootSlot: 19,
|
RootSlot: 19,
|
||||||
VotePubkey: "4ZT31jkAGhUaw8jsy4bTknwBMP8i4Eueh52By4zXcsVw",
|
VotePubkey: "CCC",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Delinquent: []rpc.VoteAccount{
|
Delinquent: []rpc.VoteAccount{
|
||||||
|
@ -112,13 +115,40 @@ func (c *staticRPCClient) GetVoteAccounts(
|
||||||
},
|
},
|
||||||
EpochVoteAccount: true,
|
EpochVoteAccount: true,
|
||||||
LastVote: 92,
|
LastVote: 92,
|
||||||
NodePubkey: "4MUdt8D2CadJKeJ8Fv2sz4jXU9xv4t2aBPpTf6TN8bae",
|
NodePubkey: "aaa",
|
||||||
RootSlot: 3,
|
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
|
//goland:noinspection GoUnusedParameter
|
||||||
|
@ -130,6 +160,183 @@ func (c *staticRPCClient) GetBlockProduction(
|
||||||
return staticBlockProduction, nil
|
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
|
// extractName takes a Prometheus descriptor and returns its name
|
||||||
func extractName(desc *prometheus.Desc) string {
|
func extractName(desc *prometheus.Desc) string {
|
||||||
// Get the string representation of the descriptor
|
// Get the string representation of the descriptor
|
||||||
|
@ -148,110 +355,24 @@ func extractName(desc *prometheus.Desc) string {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
type (
|
type collectionTest struct {
|
||||||
slotInfo struct {
|
Name string
|
||||||
leader string
|
ExpectedResponse 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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dynamicRPCClient) newSlot() {
|
func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases []collectionTest) {
|
||||||
c.slot++
|
for _, test := range testCases {
|
||||||
|
t.Run(
|
||||||
// leader changes every 4 slots
|
test.Name,
|
||||||
if c.slot%4 == 0 {
|
func(t *testing.T) {
|
||||||
c.leaderIndex = (c.leaderIndex + 1) % n
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//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.
|
// GetVersion retrieves the version of the Solana node.
|
||||||
// The method takes a context for cancellation.
|
// 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.
|
// 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) {
|
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{}{}))
|
body, err := c.rpcRequest(ctx, formatRPCRequest("getVersion", []interface{}{}))
|
||||||
|
|
||||||
if body == nil {
|
if body == nil {
|
||||||
return nil, fmt.Errorf("RPC call failed: Body empty")
|
return "", fmt.Errorf("RPC call failed: Body empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
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))
|
klog.V(2).Infof("version response: %v", string(body))
|
||||||
|
|
||||||
var resp GetVersionResponse
|
var resp GetVersionResponse
|
||||||
if err = json.Unmarshal(body, &resp); err != nil {
|
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 {
|
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