Merge pull request #23 from asymmetric-research/rpc-refactor
RPC Refactor (5)
This commit is contained in:
commit
c53c7c7b9e
|
@ -1,246 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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) {
|
||||
// reset metrics before running tests:
|
||||
leaderSlotsTotal.Reset()
|
||||
leaderSlotsByEpoch.Reset()
|
||||
|
||||
// create clients:
|
||||
client := newDynamicRPCClient()
|
||||
collector := createSolanaCollector(client, 300*time.Millisecond)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
// start client/collector and wait a bit:
|
||||
runCtx, runCancel := context.WithCancel(context.Background())
|
||||
defer runCancel()
|
||||
go client.Run(runCtx)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
slotsCtx, slotsCancel := context.WithCancel(context.Background())
|
||||
defer slotsCancel()
|
||||
go collector.WatchSlots(slotsCtx)
|
||||
time.Sleep(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(time.Second)
|
||||
final := getSlotMetricValues()
|
||||
|
||||
// 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)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.SlotHeight),
|
||||
client.Slot,
|
||||
"Exporter slot (%v) ahead of client slot (%v)!",
|
||||
int(final.SlotHeight),
|
||||
client.Slot,
|
||||
)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.TotalTransactions),
|
||||
client.TransactionCount,
|
||||
"Exporter transaction count (%v) ahead of client transaction count (%v)!",
|
||||
int(final.TotalTransactions),
|
||||
client.TransactionCount,
|
||||
)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.EpochNumber),
|
||||
client.Epoch,
|
||||
"Exporter epoch (%v) ahead of client epoch (%v)!",
|
||||
int(final.EpochNumber),
|
||||
client.Epoch,
|
||||
)
|
||||
|
||||
// check if epoch changed
|
||||
if final.EpochNumber > initial.EpochNumber {
|
||||
epochChanged = true
|
||||
}
|
||||
|
||||
// make current final the new initial (for next iteration)
|
||||
initial = final
|
||||
}
|
||||
|
||||
// epoch should have changed somewhere
|
||||
assert.Truef(t, epochChanged, "Epoch has not changed!")
|
||||
}
|
||||
|
||||
func assertSlotMetricsChangeCorrectly(t *testing.T, initial slotMetricValues, final slotMetricValues) {
|
||||
// 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,
|
||||
)
|
||||
}
|
|
@ -140,8 +140,8 @@ func (c *staticRPCClient) GetBlockProduction(
|
|||
ctx context.Context,
|
||||
firstSlot *int64,
|
||||
lastSlot *int64,
|
||||
) (rpc.BlockProduction, error) {
|
||||
return staticBlockProduction, nil
|
||||
) (*rpc.BlockProduction, error) {
|
||||
return &staticBlockProduction, nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -299,7 +299,7 @@ func (c *dynamicRPCClient) GetBlockProduction(
|
|||
ctx context.Context,
|
||||
firstSlot *int64,
|
||||
lastSlot *int64,
|
||||
) (rpc.BlockProduction, error) {
|
||||
) (*rpc.BlockProduction, error) {
|
||||
hostProduction := make(map[string]rpc.BlockProductionPerHost)
|
||||
for _, identity := range identities {
|
||||
hostProduction[identity] = rpc.BlockProductionPerHost{LeaderSlots: 0, BlocksProduced: 0}
|
||||
|
@ -313,11 +313,12 @@ func (c *dynamicRPCClient) GetBlockProduction(
|
|||
}
|
||||
hostProduction[info.leader] = hp
|
||||
}
|
||||
return rpc.BlockProduction{
|
||||
production := rpc.BlockProduction{
|
||||
FirstSlot: *firstSlot,
|
||||
LastSlot: *lastSlot,
|
||||
Hosts: hostProduction,
|
||||
}, nil
|
||||
}
|
||||
return &production, nil
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -349,7 +350,195 @@ func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases
|
|||
for _, test := range testCases {
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
err := testutil.CollectAndCompare(collector, bytes.NewBufferString(test.ExpectedResponse), test.Name)
|
||||
assert.Nilf(t, "unexpected collecting result for %s: \n%s", test.Name, err)
|
||||
assert.Nilf(t, err, "unexpected collecting result for %s: \n%s", test.Name, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolanaCollector_Collect_Static(t *testing.T) {
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
slotPacerSchedule,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
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"} 49
|
||||
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 42
|
||||
solana_validator_activated_stake{nodekey="ccc",pubkey="CCC"} 43
|
||||
`,
|
||||
},
|
||||
{
|
||||
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="aaa",pubkey="AAA"} 92
|
||||
solana_validator_last_vote{nodekey="bbb",pubkey="BBB"} 147
|
||||
solana_validator_last_vote{nodekey="ccc",pubkey="CCC"} 148
|
||||
`,
|
||||
},
|
||||
{
|
||||
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"} 3
|
||||
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 18
|
||||
solana_validator_root_slot{nodekey="ccc",pubkey="CCC"} 19
|
||||
`,
|
||||
},
|
||||
{
|
||||
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"} 1
|
||||
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="1.16.7"} 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
runCollectionTests(t, collector, testCases)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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 testBlockProductionMetric(
|
||||
t *testing.T,
|
||||
metric *prometheus.CounterVec,
|
||||
host string,
|
||||
status string,
|
||||
) {
|
||||
hostInfo := staticBlockProduction.Hosts[host]
|
||||
// get expected value depending on status:
|
||||
var expectedValue float64
|
||||
switch status {
|
||||
case "valid":
|
||||
expectedValue = float64(hostInfo.BlocksProduced)
|
||||
case "skipped":
|
||||
expectedValue = float64(hostInfo.LeaderSlots - hostInfo.BlocksProduced)
|
||||
}
|
||||
// get labels (leaderSlotsByEpoch requires an extra one)
|
||||
labels := []string{status, host}
|
||||
if metric == leaderSlotsByEpoch {
|
||||
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch))
|
||||
}
|
||||
// now we can do the assertion:
|
||||
assert.Equalf(
|
||||
t,
|
||||
expectedValue,
|
||||
testutil.ToFloat64(metric.WithLabelValues(labels...)),
|
||||
"wrong value for block-production metric with labels: %s",
|
||||
labels,
|
||||
)
|
||||
}
|
||||
|
||||
func assertSlotMetricsChangeCorrectly(t *testing.T, initial slotMetricValues, final slotMetricValues) {
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
|
||||
func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||
// reset metrics before running tests:
|
||||
leaderSlotsTotal.Reset()
|
||||
leaderSlotsByEpoch.Reset()
|
||||
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
100*time.Millisecond,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go collector.WatchSlots(ctx)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
firstSlot := staticEpochInfo.AbsoluteSlot - staticEpochInfo.SlotIndex
|
||||
lastSlot := firstSlot + staticEpochInfo.SlotsInEpoch
|
||||
tests := []struct {
|
||||
expectedValue float64
|
||||
metric prometheus.Gauge
|
||||
}{
|
||||
{expectedValue: float64(staticEpochInfo.AbsoluteSlot), metric: confirmedSlotHeight},
|
||||
{expectedValue: float64(staticEpochInfo.TransactionCount), metric: totalTransactionsTotal},
|
||||
{expectedValue: float64(staticEpochInfo.Epoch), metric: currentEpochNumber},
|
||||
{expectedValue: float64(firstSlot), metric: epochFirstSlot},
|
||||
{expectedValue: float64(lastSlot), metric: epochLastSlot},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
name := extractName(testCase.metric.Desc())
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.expectedValue, testutil.ToFloat64(testCase.metric))
|
||||
})
|
||||
}
|
||||
|
||||
metrics := map[string]*prometheus.CounterVec{
|
||||
"solana_leader_slots_total": leaderSlotsTotal,
|
||||
"solana_leader_slots_by_epoch": leaderSlotsByEpoch,
|
||||
}
|
||||
statuses := []string{"valid", "skipped"}
|
||||
for name, metric := range metrics {
|
||||
// subtest for each metric:
|
||||
t.Run(name, func(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 _, identity := range identities {
|
||||
testBlockProductionMetric(t, metric, identity, status)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) {
|
||||
// reset metrics before running tests:
|
||||
leaderSlotsTotal.Reset()
|
||||
leaderSlotsByEpoch.Reset()
|
||||
|
||||
// create clients:
|
||||
client := newDynamicRPCClient()
|
||||
collector := createSolanaCollector(client, 300*time.Millisecond)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
// start client/collector and wait a bit:
|
||||
runCtx, runCancel := context.WithCancel(context.Background())
|
||||
defer runCancel()
|
||||
go client.Run(runCtx)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
slotsCtx, slotsCancel := context.WithCancel(context.Background())
|
||||
defer slotsCancel()
|
||||
go collector.WatchSlots(slotsCtx)
|
||||
time.Sleep(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(time.Second)
|
||||
final := getSlotMetricValues()
|
||||
|
||||
// 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)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.SlotHeight),
|
||||
client.Slot,
|
||||
"Exporter slot (%v) ahead of client slot (%v)!",
|
||||
int(final.SlotHeight),
|
||||
client.Slot,
|
||||
)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.TotalTransactions),
|
||||
client.TransactionCount,
|
||||
"Exporter transaction count (%v) ahead of client transaction count (%v)!",
|
||||
int(final.TotalTransactions),
|
||||
client.TransactionCount,
|
||||
)
|
||||
assert.LessOrEqualf(
|
||||
t,
|
||||
int(final.EpochNumber),
|
||||
client.Epoch,
|
||||
"Exporter epoch (%v) ahead of client epoch (%v)!",
|
||||
int(final.EpochNumber),
|
||||
client.Epoch,
|
||||
)
|
||||
|
||||
// check if epoch changed
|
||||
if final.EpochNumber > initial.EpochNumber {
|
||||
epochChanged = true
|
||||
}
|
||||
|
||||
// make current final the new initial (for next iteration)
|
||||
initial = final
|
||||
}
|
||||
|
||||
// epoch should have changed somewhere
|
||||
assert.Truef(t, epochChanged, "Epoch has not changed!")
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSolanaCollector_Collect_Static(t *testing.T) {
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
slotPacerSchedule,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
|
||||
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"} 49
|
||||
solana_validator_activated_stake{nodekey="bbb",pubkey="BBB"} 42
|
||||
solana_validator_activated_stake{nodekey="ccc",pubkey="CCC"} 43
|
||||
`,
|
||||
},
|
||||
{
|
||||
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="aaa",pubkey="AAA"} 92
|
||||
solana_validator_last_vote{nodekey="bbb",pubkey="BBB"} 147
|
||||
solana_validator_last_vote{nodekey="ccc",pubkey="CCC"} 148
|
||||
`,
|
||||
},
|
||||
{
|
||||
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"} 3
|
||||
solana_validator_root_slot{nodekey="bbb",pubkey="BBB"} 18
|
||||
solana_validator_root_slot{nodekey="ccc",pubkey="CCC"} 19
|
||||
`,
|
||||
},
|
||||
{
|
||||
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"} 1
|
||||
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="1.16.7"} 1
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
runCollectionTests(t, collector, testCases)
|
||||
}
|
||||
|
||||
func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||
// reset metrics before running tests:
|
||||
leaderSlotsTotal.Reset()
|
||||
leaderSlotsByEpoch.Reset()
|
||||
|
||||
collector := createSolanaCollector(
|
||||
&staticRPCClient{},
|
||||
100*time.Millisecond,
|
||||
)
|
||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go collector.WatchSlots(ctx)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
firstSlot := staticEpochInfo.AbsoluteSlot - staticEpochInfo.SlotIndex
|
||||
lastSlot := firstSlot + staticEpochInfo.SlotsInEpoch
|
||||
tests := []struct {
|
||||
expectedValue float64
|
||||
metric prometheus.Gauge
|
||||
}{
|
||||
{expectedValue: float64(staticEpochInfo.AbsoluteSlot), metric: confirmedSlotHeight},
|
||||
{expectedValue: float64(staticEpochInfo.TransactionCount), metric: totalTransactionsTotal},
|
||||
{expectedValue: float64(staticEpochInfo.Epoch), metric: currentEpochNumber},
|
||||
{expectedValue: float64(firstSlot), metric: epochFirstSlot},
|
||||
{expectedValue: float64(lastSlot), metric: epochLastSlot},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
name := extractName(testCase.metric.Desc())
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, testCase.expectedValue, testutil.ToFloat64(testCase.metric))
|
||||
})
|
||||
}
|
||||
|
||||
metrics := map[string]*prometheus.CounterVec{
|
||||
"solana_leader_slots_total": leaderSlotsTotal,
|
||||
"solana_leader_slots_by_epoch": leaderSlotsByEpoch,
|
||||
}
|
||||
statuses := []string{"valid", "skipped"}
|
||||
for name, metric := range metrics {
|
||||
// subtest for each metric:
|
||||
t.Run(name, func(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 _, identity := range identities {
|
||||
testBlockProductionMetric(t, metric, identity, status)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testBlockProductionMetric(
|
||||
t *testing.T,
|
||||
metric *prometheus.CounterVec,
|
||||
host string,
|
||||
status string,
|
||||
) {
|
||||
hostInfo := staticBlockProduction.Hosts[host]
|
||||
// get expected value depending on status:
|
||||
var expectedValue float64
|
||||
switch status {
|
||||
case "valid":
|
||||
expectedValue = float64(hostInfo.BlocksProduced)
|
||||
case "skipped":
|
||||
expectedValue = float64(hostInfo.LeaderSlots - hostInfo.BlocksProduced)
|
||||
}
|
||||
// get labels (leaderSlotsByEpoch requires an extra one)
|
||||
labels := []string{status, host}
|
||||
if metric == leaderSlotsByEpoch {
|
||||
labels = append(labels, fmt.Sprintf("%d", staticEpochInfo.Epoch))
|
||||
}
|
||||
// now we can do the assertion:
|
||||
assert.Equalf(
|
||||
t,
|
||||
expectedValue,
|
||||
testutil.ToFloat64(metric.WithLabelValues(labels...)),
|
||||
"wrong value for block-production metric with labels: %s",
|
||||
labels,
|
||||
)
|
||||
}
|
3
go.mod
3
go.mod
|
@ -13,9 +13,12 @@ require (
|
|||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.48.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
119
go.sum
119
go.sum
|
@ -1,119 +1,40 @@
|
|||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
|
||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.4.0 h1:YVIb/fVcOTMSqtqZWSKnHpSLBxu8DKgxq8z6RuBZwqI=
|
||||
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
|
||||
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
|
||||
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
|
||||
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=
|
||||
k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
getBlockProductionConfig struct {
|
||||
Range getBlockProductionRange `json:"range,omitempty"`
|
||||
}
|
||||
|
||||
getBlockProductionRange struct {
|
||||
FirstSlot int64 `json:"firstSlot"`
|
||||
LastSlot *int64 `json:"lastSlot,omitempty"`
|
||||
}
|
||||
|
||||
getBlockProductionValue struct {
|
||||
ByIdentity map[string][]int64 `json:"byIdentity"`
|
||||
Range getBlockProductionRange `json:"range"`
|
||||
}
|
||||
|
||||
getBlockProductionResult struct {
|
||||
Value getBlockProductionValue `json:"value"`
|
||||
}
|
||||
|
||||
getBlockProductionResponse struct {
|
||||
Result getBlockProductionResult `json:"result"`
|
||||
Error rpcError2 `json:"error"`
|
||||
}
|
||||
|
||||
BlockProductionPerHost struct {
|
||||
LeaderSlots int64
|
||||
BlocksProduced int64
|
||||
}
|
||||
|
||||
BlockProduction struct {
|
||||
FirstSlot int64
|
||||
LastSlot int64
|
||||
Hosts map[string]BlockProductionPerHost
|
||||
}
|
||||
)
|
||||
|
||||
// https://solana.com/docs/rpc/http/getblockproduction
|
||||
func (c *Client) GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (BlockProduction, error) {
|
||||
config := make([]interface{}, 0, 1)
|
||||
if firstSlot != nil {
|
||||
config = append(config,
|
||||
getBlockProductionConfig{
|
||||
Range: getBlockProductionRange{
|
||||
FirstSlot: *firstSlot,
|
||||
LastSlot: lastSlot,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
ret := BlockProduction{
|
||||
FirstSlot: 0,
|
||||
LastSlot: 0,
|
||||
Hosts: nil,
|
||||
}
|
||||
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getBlockProduction", config))
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("RPC call failed: %w", err)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("getBlockProduction response: %v", string(body))
|
||||
|
||||
var resp getBlockProductionResponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return ret, fmt.Errorf("failed to decode response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.Error.Code != 0 {
|
||||
return ret, fmt.Errorf("RPC error: %d %v", resp.Error.Code, resp.Error.Message)
|
||||
}
|
||||
|
||||
ret.FirstSlot = resp.Result.Value.Range.FirstSlot
|
||||
ret.LastSlot = *resp.Result.Value.Range.LastSlot
|
||||
ret.Hosts = make(map[string]BlockProductionPerHost)
|
||||
|
||||
for id, arr := range resp.Result.Value.ByIdentity {
|
||||
ret.Hosts[id] = BlockProductionPerHost{
|
||||
LeaderSlots: arr[0],
|
||||
BlocksProduced: arr[1],
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"k8s.io/klog/v2"
|
||||
"net/http"
|
||||
|
@ -15,12 +16,7 @@ type (
|
|||
rpcAddr string
|
||||
}
|
||||
|
||||
rpcError1 struct {
|
||||
Message string `json:"message"`
|
||||
Code int64 `json:"id"`
|
||||
}
|
||||
|
||||
rpcError2 struct { // TODO: combine these error types into a single one
|
||||
rpcError struct {
|
||||
Message string `json:"message"`
|
||||
Code int64 `json:"code"`
|
||||
}
|
||||
|
@ -42,7 +38,7 @@ type Provider interface {
|
|||
// GetBlockProduction retrieves the block production information for the specified slot range.
|
||||
// The method takes a context for cancellation, and pointers to the first and last slots of the range.
|
||||
// It returns a BlockProduction struct containing the block production details, or an error if the operation fails.
|
||||
GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (BlockProduction, error)
|
||||
GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (*BlockProduction, error)
|
||||
|
||||
// GetEpochInfo retrieves the information regarding the current epoch.
|
||||
// The method takes a context for cancellation and a commitment level to specify the desired state.
|
||||
|
@ -56,13 +52,13 @@ type Provider interface {
|
|||
|
||||
// GetVoteAccounts retrieves the vote accounts information.
|
||||
// The method takes a context for cancellation and a slice of parameters to filter the vote accounts.
|
||||
// It returns a pointer to a GetVoteAccountsResponse struct containing the vote accounts details,
|
||||
// It returns a pointer to a VoteAccounts struct containing the vote accounts details,
|
||||
// or an error if the operation fails.
|
||||
GetVoteAccounts(ctx context.Context, params []interface{}) (*VoteAccounts, error)
|
||||
|
||||
// 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.
|
||||
// It returns a string containing the version information, or an error if the operation fails.
|
||||
GetVersion(ctx context.Context) (string, error)
|
||||
}
|
||||
|
||||
|
@ -82,29 +78,29 @@ const (
|
|||
)
|
||||
|
||||
func NewRPCClient(rpcAddr string) *Client {
|
||||
c := &Client{
|
||||
client := &Client{
|
||||
httpClient: http.Client{},
|
||||
rpcAddr: rpcAddr,
|
||||
}
|
||||
|
||||
return c
|
||||
return client
|
||||
}
|
||||
|
||||
func formatRPCRequest(method string, params []interface{}) io.Reader {
|
||||
r := &rpcRequest{
|
||||
request := &rpcRequest{
|
||||
Version: "2.0",
|
||||
ID: 1,
|
||||
Method: method,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(r)
|
||||
buffer, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("jsonrpc request: %s", string(b))
|
||||
return bytes.NewBuffer(b)
|
||||
klog.V(2).Infof("jsonrpc request: %s", string(buffer))
|
||||
return bytes.NewBuffer(buffer)
|
||||
}
|
||||
|
||||
func (c *Client) rpcRequest(ctx context.Context, data io.Reader) ([]byte, error) {
|
||||
|
@ -128,3 +124,92 @@ func (c *Client) rpcRequest(ctx context.Context, data io.Reader) ([]byte, error)
|
|||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
func (c *Client) getResponse(ctx context.Context, method string, params []interface{}, result HasRPCError) error {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest(method, params))
|
||||
// check if there was an error making the request:
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s RPC call failed: %w", method, err)
|
||||
}
|
||||
// log response:
|
||||
klog.V(2).Infof("%s response: %v", method, string(body))
|
||||
|
||||
// unmarshal the response into the predicted format
|
||||
if err = json.Unmarshal(body, result); err != nil {
|
||||
return fmt.Errorf("failed to decode %s response body: %w", method, err)
|
||||
}
|
||||
|
||||
if result.getError().Code != 0 {
|
||||
return fmt.Errorf("RPC error: %d %v", result.getError().Code, result.getError().Message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) GetEpochInfo(ctx context.Context, commitment Commitment) (*EpochInfo, error) {
|
||||
var resp response[EpochInfo]
|
||||
if err := c.getResponse(ctx, "getEpochInfo", []interface{}{commitment}, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp.Result, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetVoteAccounts(ctx context.Context, params []interface{}) (*VoteAccounts, error) {
|
||||
var resp response[VoteAccounts]
|
||||
if err := c.getResponse(ctx, "getVoteAccounts", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp.Result, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetVersion(ctx context.Context) (string, error) {
|
||||
var resp response[struct {
|
||||
Version string `json:"solana-core"`
|
||||
}]
|
||||
if err := c.getResponse(ctx, "getVersion", []interface{}{}, &resp); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Result.Version, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetSlot(ctx context.Context) (int64, error) {
|
||||
var resp response[int64]
|
||||
if err := c.getResponse(ctx, "getSlot", []interface{}{}, &resp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return resp.Result, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBlockProduction(ctx context.Context, firstSlot *int64, lastSlot *int64) (*BlockProduction, error) {
|
||||
// format params:
|
||||
params := make([]interface{}, 1)
|
||||
if firstSlot != nil {
|
||||
params[0] = map[string]interface{}{
|
||||
"range": blockProductionRange{
|
||||
FirstSlot: *firstSlot,
|
||||
LastSlot: lastSlot,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// make request:
|
||||
var resp response[blockProductionResult]
|
||||
if err := c.getResponse(ctx, "getBlockProduction", params, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert to BlockProduction format:
|
||||
hosts := make(map[string]BlockProductionPerHost)
|
||||
for id, arr := range resp.Result.Value.ByIdentity {
|
||||
hosts[id] = BlockProductionPerHost{
|
||||
LeaderSlots: arr[0],
|
||||
BlocksProduced: arr[1],
|
||||
}
|
||||
}
|
||||
production := BlockProduction{
|
||||
FirstSlot: resp.Result.Value.Range.FirstSlot,
|
||||
LastSlot: *resp.Result.Value.Range.LastSlot,
|
||||
Hosts: hosts,
|
||||
}
|
||||
return &production, nil
|
||||
}
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
EpochInfo struct {
|
||||
// Current absolute slot in epoch
|
||||
AbsoluteSlot int64 `json:"absoluteSlot"`
|
||||
// Current block height
|
||||
BlockHeight int64 `json:"blockHeight"`
|
||||
// Current epoch number
|
||||
Epoch int64 `json:"epoch"`
|
||||
// Current slot relative to the start of the current epoch
|
||||
SlotIndex int64 `json:"slotIndex"`
|
||||
// Number of slots in this epoch
|
||||
SlotsInEpoch int64 `json:"slotsInEpoch"`
|
||||
// Total number of transactions ever (?)
|
||||
TransactionCount int64 `json:"transactionCount"`
|
||||
}
|
||||
|
||||
GetEpochInfoResponse struct {
|
||||
Result EpochInfo `json:"result"`
|
||||
Error rpcError1 `json:"error"`
|
||||
}
|
||||
)
|
||||
|
||||
// https://docs.solana.com/developing/clients/jsonrpc-api#getepochinfo
|
||||
func (c *Client) GetEpochInfo(ctx context.Context, commitment Commitment) (*EpochInfo, error) {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getEpochInfo", []interface{}{commitment}))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RPC call failed: %w", err)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("epoch info response: %v", string(body))
|
||||
|
||||
var resp GetEpochInfoResponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, 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 &resp.Result, nil
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package rpc
|
||||
|
||||
type (
|
||||
response[T any] struct {
|
||||
Result T `json:"result"`
|
||||
Error rpcError `json:"error"`
|
||||
}
|
||||
|
||||
EpochInfo struct {
|
||||
// Current absolute slot in epoch
|
||||
AbsoluteSlot int64 `json:"absoluteSlot"`
|
||||
// Current block height
|
||||
BlockHeight int64 `json:"blockHeight"`
|
||||
// Current epoch number
|
||||
Epoch int64 `json:"epoch"`
|
||||
// Current slot relative to the start of the current epoch
|
||||
SlotIndex int64 `json:"slotIndex"`
|
||||
// Number of slots in this epoch
|
||||
SlotsInEpoch int64 `json:"slotsInEpoch"`
|
||||
// Total number of transactions
|
||||
TransactionCount int64 `json:"transactionCount"`
|
||||
}
|
||||
|
||||
VoteAccount struct {
|
||||
ActivatedStake int64 `json:"activatedStake"`
|
||||
Commission int `json:"commission"`
|
||||
EpochCredits [][]int `json:"epochCredits"`
|
||||
EpochVoteAccount bool `json:"epochVoteAccount"`
|
||||
LastVote int `json:"lastVote"`
|
||||
NodePubkey string `json:"nodePubkey"`
|
||||
RootSlot int `json:"rootSlot"`
|
||||
VotePubkey string `json:"votePubkey"`
|
||||
}
|
||||
|
||||
VoteAccounts struct {
|
||||
Current []VoteAccount `json:"current"`
|
||||
Delinquent []VoteAccount `json:"delinquent"`
|
||||
}
|
||||
|
||||
blockProductionRange struct {
|
||||
FirstSlot int64 `json:"firstSlot"`
|
||||
LastSlot *int64 `json:"lastSlot,omitempty"`
|
||||
}
|
||||
|
||||
blockProductionResult struct {
|
||||
Value struct {
|
||||
ByIdentity map[string][]int64 `json:"byIdentity"`
|
||||
Range blockProductionRange `json:"range"`
|
||||
} `json:"value"`
|
||||
}
|
||||
|
||||
BlockProductionPerHost struct {
|
||||
LeaderSlots int64
|
||||
BlocksProduced int64
|
||||
}
|
||||
|
||||
BlockProduction struct {
|
||||
FirstSlot int64
|
||||
LastSlot int64
|
||||
Hosts map[string]BlockProductionPerHost
|
||||
}
|
||||
)
|
||||
|
||||
func (r response[T]) getError() rpcError {
|
||||
return r.Error
|
||||
}
|
||||
|
||||
type HasRPCError interface {
|
||||
getError() rpcError
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type getSlotResponse struct {
|
||||
Result int64 `json:"result"`
|
||||
}
|
||||
|
||||
// https://solana.com/docs/rpc/http/getslot
|
||||
func (c *Client) GetSlot(ctx context.Context) (int64, error) {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getSlot", []interface{}{}))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("RPC call failed: %w", err)
|
||||
}
|
||||
|
||||
klog.V(2).Infof("getSlot response: %v", string(body))
|
||||
|
||||
var resp getSlotResponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return 0, fmt.Errorf("failed to decode response body: %w", err)
|
||||
}
|
||||
|
||||
return resp.Result, nil
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
VoteAccount struct {
|
||||
ActivatedStake int64 `json:"activatedStake"`
|
||||
Commission int `json:"commission"`
|
||||
EpochCredits [][]int `json:"epochCredits"`
|
||||
EpochVoteAccount bool `json:"epochVoteAccount"`
|
||||
LastVote int `json:"lastVote"`
|
||||
NodePubkey string `json:"nodePubkey"`
|
||||
RootSlot int `json:"rootSlot"`
|
||||
VotePubkey string `json:"votePubkey"`
|
||||
}
|
||||
|
||||
VoteAccounts struct {
|
||||
Current []VoteAccount `json:"current"`
|
||||
Delinquent []VoteAccount `json:"delinquent"`
|
||||
}
|
||||
|
||||
GetVoteAccountsResponse struct {
|
||||
Result VoteAccounts `json:"result"`
|
||||
Error rpcError1 `json:"error"`
|
||||
}
|
||||
)
|
||||
|
||||
// https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts
|
||||
func (c *Client) GetVoteAccounts(ctx context.Context, params []interface{}) (*VoteAccounts, error) {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getVoteAccounts", params))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RPC call failed: %w", err)
|
||||
}
|
||||
|
||||
klog.V(3).Infof("getVoteAccounts response: %v", string(body))
|
||||
|
||||
var resp GetVoteAccountsResponse
|
||||
if err = json.Unmarshal(body, &resp); err != nil {
|
||||
return nil, 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 &resp.Result, nil
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type (
|
||||
GetVersionResponse struct {
|
||||
Result struct {
|
||||
Version string `json:"solana-core"`
|
||||
} `json:"result"`
|
||||
Error rpcError1 `json:"error"`
|
||||
}
|
||||
)
|
||||
|
||||
func (c *Client) GetVersion(ctx context.Context) (string, error) {
|
||||
body, err := c.rpcRequest(ctx, formatRPCRequest("getVersion", []interface{}{}))
|
||||
|
||||
if body == nil {
|
||||
return "", fmt.Errorf("RPC call failed: Body empty")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
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 "", fmt.Errorf("failed to decode response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.Error.Code != 0 {
|
||||
return "", fmt.Errorf("RPC error: %d %v", resp.Error.Code, resp.Error.Message)
|
||||
}
|
||||
|
||||
return resp.Result.Version, nil
|
||||
}
|
Loading…
Reference in New Issue