Merge pull request #36 from asymmetric-research/inflation-rewards
Added inflation rewards
This commit is contained in:
commit
d77b929cfa
|
@ -14,17 +14,28 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
httpTimeout = 60 * time.Second
|
httpTimeout = 60 * time.Second
|
||||||
rpcAddr = flag.String("rpcURI", "", "Solana RPC URI (including protocol and path)")
|
rpcAddr = flag.String("rpcURI", "", "Solana RPC URI (including protocol and path)")
|
||||||
addr = flag.String("addr", ":8080", "Listen address")
|
addr = flag.String("addr", ":8080", "Listen address")
|
||||||
votePubkey = flag.String("votepubkey", "", "Validator vote address (will only return results of this address)")
|
votePubkey = flag.String("votepubkey", "", "Validator vote address (will only return results of this address)")
|
||||||
httpTimeoutSecs = flag.Int("http_timeout", 60, "HTTP timeout in seconds")
|
httpTimeoutSecs = flag.Int("http_timeout", 60, "HTTP timeout in seconds")
|
||||||
balanceAddresses = flag.String("balance-addresses", "", "Comma-separated list of addresses to monitor balances")
|
|
||||||
|
// addresses:
|
||||||
|
balanceAddresses = flag.String(
|
||||||
|
"balance-addresses",
|
||||||
|
"",
|
||||||
|
"Comma-separated list of addresses to monitor SOL balances.",
|
||||||
|
)
|
||||||
leaderSlotAddresses = flag.String(
|
leaderSlotAddresses = flag.String(
|
||||||
"leader-slot-addresses",
|
"leader-slot-addresses",
|
||||||
"",
|
"",
|
||||||
"Comma-separated list of addresses to monitor leader slots by epoch for, leave nil to track by epoch for all validators (this creates a lot of Prometheus metrics with every new epoch).",
|
"Comma-separated list of addresses to monitor leader slots by epoch for, leave nil to track by epoch for all validators (this creates a lot of Prometheus metrics with every new epoch).",
|
||||||
)
|
)
|
||||||
|
inflationRewardAddresses = flag.String(
|
||||||
|
"inflation-reward-addresses",
|
||||||
|
"",
|
||||||
|
"Comma-separated list of validator vote accounts to track inflationary rewards for",
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -35,9 +46,10 @@ type solanaCollector struct {
|
||||||
rpcClient rpc.Provider
|
rpcClient rpc.Provider
|
||||||
|
|
||||||
// config:
|
// config:
|
||||||
slotPace time.Duration
|
slotPace time.Duration
|
||||||
balanceAddresses []string
|
balanceAddresses []string
|
||||||
leaderSlotAddresses []string
|
leaderSlotAddresses []string
|
||||||
|
inflationRewardAddresses []string
|
||||||
|
|
||||||
/// descriptors:
|
/// descriptors:
|
||||||
totalValidatorsDesc *prometheus.Desc
|
totalValidatorsDesc *prometheus.Desc
|
||||||
|
@ -50,13 +62,18 @@ type solanaCollector struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func createSolanaCollector(
|
func createSolanaCollector(
|
||||||
provider rpc.Provider, slotPace time.Duration, balanceAddresses []string, leaderSlotAddresses []string,
|
provider rpc.Provider,
|
||||||
|
slotPace time.Duration,
|
||||||
|
balanceAddresses []string,
|
||||||
|
leaderSlotAddresses []string,
|
||||||
|
inflationRewardAddresses []string,
|
||||||
) *solanaCollector {
|
) *solanaCollector {
|
||||||
return &solanaCollector{
|
return &solanaCollector{
|
||||||
rpcClient: provider,
|
rpcClient: provider,
|
||||||
slotPace: slotPace,
|
slotPace: slotPace,
|
||||||
balanceAddresses: balanceAddresses,
|
balanceAddresses: balanceAddresses,
|
||||||
leaderSlotAddresses: leaderSlotAddresses,
|
leaderSlotAddresses: leaderSlotAddresses,
|
||||||
|
inflationRewardAddresses: inflationRewardAddresses,
|
||||||
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",
|
||||||
|
@ -102,8 +119,12 @@ func createSolanaCollector(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSolanaCollector(rpcAddr string, balanceAddresses []string, leaderSlotAddresses []string) *solanaCollector {
|
func NewSolanaCollector(
|
||||||
return createSolanaCollector(rpc.NewRPCClient(rpcAddr), slotPacerSchedule, balanceAddresses, leaderSlotAddresses)
|
rpcAddr string, balanceAddresses []string, leaderSlotAddresses []string, inflationRewardAddresses []string,
|
||||||
|
) *solanaCollector {
|
||||||
|
return createSolanaCollector(
|
||||||
|
rpc.NewRPCClient(rpcAddr), slotPacerSchedule, balanceAddresses, leaderSlotAddresses, inflationRewardAddresses,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
|
func (c *solanaCollector) Describe(ch chan<- *prometheus.Desc) {
|
||||||
|
@ -233,6 +254,7 @@ func main() {
|
||||||
var (
|
var (
|
||||||
balAddresses []string
|
balAddresses []string
|
||||||
lsAddresses []string
|
lsAddresses []string
|
||||||
|
irAddresses []string
|
||||||
)
|
)
|
||||||
if *balanceAddresses != "" {
|
if *balanceAddresses != "" {
|
||||||
balAddresses = strings.Split(*balanceAddresses, ",")
|
balAddresses = strings.Split(*balanceAddresses, ",")
|
||||||
|
@ -240,8 +262,11 @@ func main() {
|
||||||
if *leaderSlotAddresses != "" {
|
if *leaderSlotAddresses != "" {
|
||||||
lsAddresses = strings.Split(*leaderSlotAddresses, ",")
|
lsAddresses = strings.Split(*leaderSlotAddresses, ",")
|
||||||
}
|
}
|
||||||
|
if *inflationRewardAddresses != "" {
|
||||||
|
irAddresses = strings.Split(*inflationRewardAddresses, ",")
|
||||||
|
}
|
||||||
|
|
||||||
collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses)
|
collector := NewSolanaCollector(*rpcAddr, balAddresses, lsAddresses, irAddresses)
|
||||||
|
|
||||||
slotWatcher := NewCollectorSlotWatcher(collector)
|
slotWatcher := NewCollectorSlotWatcher(collector)
|
||||||
go slotWatcher.WatchSlots(context.Background(), collector.slotPace)
|
go slotWatcher.WatchSlots(context.Background(), collector.slotPace)
|
||||||
|
|
|
@ -42,6 +42,7 @@ type (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
identities = []string{"aaa", "bbb", "ccc"}
|
identities = []string{"aaa", "bbb", "ccc"}
|
||||||
|
votekeys = []string{"AAA", "BBB", "CCC"}
|
||||||
balances = map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3}
|
balances = map[string]float64{"aaa": 1, "bbb": 2, "ccc": 3}
|
||||||
identityVotes = map[string]string{"aaa": "AAA", "bbb": "BBB", "ccc": "CCC"}
|
identityVotes = map[string]string{"aaa": "AAA", "bbb": "BBB", "ccc": "CCC"}
|
||||||
nv = len(identities)
|
nv = len(identities)
|
||||||
|
@ -61,6 +62,11 @@ var (
|
||||||
},
|
},
|
||||||
Range: rpc.BlockProductionRange{FirstSlot: 1000, LastSlot: 2000},
|
Range: rpc.BlockProductionRange{FirstSlot: 1000, LastSlot: 2000},
|
||||||
}
|
}
|
||||||
|
staticInflationRewards = []rpc.InflationReward{
|
||||||
|
{Amount: 1000, EffectiveSlot: 166598, Epoch: 27, PostBalance: 2000},
|
||||||
|
{Amount: 2000, EffectiveSlot: 166598, Epoch: 27, PostBalance: 4000},
|
||||||
|
{Amount: 3000, EffectiveSlot: 166598, Epoch: 27, PostBalance: 6000},
|
||||||
|
}
|
||||||
staticVoteAccounts = rpc.VoteAccounts{
|
staticVoteAccounts = rpc.VoteAccounts{
|
||||||
Current: []rpc.VoteAccount{
|
Current: []rpc.VoteAccount{
|
||||||
{
|
{
|
||||||
|
@ -147,6 +153,13 @@ func (c *staticRPCClient) GetBalance(ctx context.Context, address string) (float
|
||||||
return balances[address], nil
|
return balances[address], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//goland:noinspection GoUnusedParameter
|
||||||
|
func (c *staticRPCClient) GetInflationReward(
|
||||||
|
ctx context.Context, addresses []string, commitment rpc.Commitment, epoch *int64, minContextSlot *int64,
|
||||||
|
) ([]rpc.InflationReward, error) {
|
||||||
|
return staticInflationRewards, nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
===== DYNAMIC CLIENT =====:
|
===== DYNAMIC CLIENT =====:
|
||||||
*/
|
*/
|
||||||
|
@ -321,6 +334,13 @@ func (c *dynamicRPCClient) GetBalance(ctx context.Context, address string) (floa
|
||||||
return balances[address], nil
|
return balances[address], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//goland:noinspection GoUnusedParameter
|
||||||
|
func (c *dynamicRPCClient) GetInflationReward(
|
||||||
|
ctx context.Context, addresses []string, commitment rpc.Commitment, epoch *int64, minContextSlot *int64,
|
||||||
|
) ([]rpc.InflationReward, error) {
|
||||||
|
return staticInflationRewards, nil
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
===== OTHER TEST UTILITIES =====:
|
===== OTHER TEST UTILITIES =====:
|
||||||
*/
|
*/
|
||||||
|
@ -356,7 +376,7 @@ func runCollectionTests(t *testing.T, collector prometheus.Collector, testCases
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSolanaCollector_Collect_Static(t *testing.T) {
|
func TestSolanaCollector_Collect_Static(t *testing.T) {
|
||||||
collector := createSolanaCollector(&staticRPCClient{}, slotPacerSchedule, identities, []string{})
|
collector := createSolanaCollector(&staticRPCClient{}, slotPacerSchedule, identities, []string{}, votekeys)
|
||||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||||
|
|
||||||
testCases := []collectionTest{
|
testCases := []collectionTest{
|
||||||
|
@ -434,7 +454,7 @@ solana_account_balance{address="ccc"} 3
|
||||||
|
|
||||||
func TestSolanaCollector_Collect_Dynamic(t *testing.T) {
|
func TestSolanaCollector_Collect_Dynamic(t *testing.T) {
|
||||||
client := newDynamicRPCClient()
|
client := newDynamicRPCClient()
|
||||||
collector := createSolanaCollector(client, slotPacerSchedule, identities, []string{})
|
collector := createSolanaCollector(client, slotPacerSchedule, identities, []string{}, votekeys)
|
||||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||||
|
|
||||||
// start off by testing initial state:
|
// start off by testing initial state:
|
||||||
|
|
|
@ -18,7 +18,9 @@ const (
|
||||||
type SlotWatcher struct {
|
type SlotWatcher struct {
|
||||||
client rpc.Provider
|
client rpc.Provider
|
||||||
|
|
||||||
leaderSlotAddresses []string
|
// config:
|
||||||
|
leaderSlotAddresses []string
|
||||||
|
inflationRewardAddresses []string
|
||||||
|
|
||||||
// currentEpoch is the current epoch we are watching
|
// currentEpoch is the current epoch we are watching
|
||||||
currentEpoch int64
|
currentEpoch int64
|
||||||
|
@ -71,10 +73,22 @@ var (
|
||||||
},
|
},
|
||||||
[]string{"status", "nodekey", "epoch"},
|
[]string{"status", "nodekey", "epoch"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
inflationRewards = prometheus.NewGaugeVec(
|
||||||
|
prometheus.GaugeOpts{
|
||||||
|
Name: "solana_inflation_rewards",
|
||||||
|
Help: "Inflation reward earned per validator vote account, per epoch",
|
||||||
|
},
|
||||||
|
[]string{"votekey", "epoch"},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCollectorSlotWatcher(collector *solanaCollector) *SlotWatcher {
|
func NewCollectorSlotWatcher(collector *solanaCollector) *SlotWatcher {
|
||||||
return &SlotWatcher{client: collector.rpcClient, leaderSlotAddresses: collector.leaderSlotAddresses}
|
return &SlotWatcher{
|
||||||
|
client: collector.rpcClient,
|
||||||
|
leaderSlotAddresses: collector.leaderSlotAddresses,
|
||||||
|
inflationRewardAddresses: collector.inflationRewardAddresses,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -85,6 +99,7 @@ func init() {
|
||||||
prometheus.MustRegister(epochLastSlot)
|
prometheus.MustRegister(epochLastSlot)
|
||||||
prometheus.MustRegister(leaderSlotsTotal)
|
prometheus.MustRegister(leaderSlotsTotal)
|
||||||
prometheus.MustRegister(leaderSlotsByEpoch)
|
prometheus.MustRegister(leaderSlotsByEpoch)
|
||||||
|
prometheus.MustRegister(inflationRewards)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) {
|
func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) {
|
||||||
|
@ -124,6 +139,13 @@ func (c *SlotWatcher) WatchSlots(ctx context.Context, pace time.Duration) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if epochInfo.Epoch > c.currentEpoch {
|
if epochInfo.Epoch > c.currentEpoch {
|
||||||
|
// if we have configured inflation reward addresses, fetch em
|
||||||
|
if len(c.inflationRewardAddresses) > 0 {
|
||||||
|
err = c.fetchAndEmitInflationRewards(ctx, c.currentEpoch)
|
||||||
|
if err != nil {
|
||||||
|
klog.Errorf("Failed to emit inflation rewards, bailing out: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
c.closeCurrentEpoch(ctx, epochInfo)
|
c.closeCurrentEpoch(ctx, epochInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,3 +267,28 @@ func getEpochBounds(info *rpc.EpochInfo) (int64, int64) {
|
||||||
firstSlot := info.AbsoluteSlot - info.SlotIndex
|
firstSlot := info.AbsoluteSlot - info.SlotIndex
|
||||||
return firstSlot, firstSlot + info.SlotsInEpoch - 1
|
return firstSlot, firstSlot + info.SlotsInEpoch - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetchAndEmitInflationRewards fetches and emits the inflation rewards for the configured inflationRewardAddresses
|
||||||
|
// at the provided epoch
|
||||||
|
func (c *SlotWatcher) fetchAndEmitInflationRewards(ctx context.Context, epoch int64) error {
|
||||||
|
epochStr := fmt.Sprintf("%d", epoch)
|
||||||
|
klog.Infof("Fetching inflation reward for epoch %v ...", epochStr)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, httpTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
rewardInfos, err := c.client.GetInflationReward(
|
||||||
|
ctx, c.inflationRewardAddresses, rpc.CommitmentFinalized, &epoch, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetching inflation rewards: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rewardInfo := range rewardInfos {
|
||||||
|
address := c.inflationRewardAddresses[i]
|
||||||
|
reward := float64(rewardInfo.Amount) / float64(rpc.LamportsInSol)
|
||||||
|
inflationRewards.WithLabelValues(address, epochStr).Set(reward)
|
||||||
|
}
|
||||||
|
klog.Infof("Fetched inflation reward for epoch %v.", epochStr)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/asymmetric-research/solana_exporter/pkg/rpc"
|
||||||
"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"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -91,19 +92,24 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||||
leaderSlotsTotal.Reset()
|
leaderSlotsTotal.Reset()
|
||||||
leaderSlotsByEpoch.Reset()
|
leaderSlotsByEpoch.Reset()
|
||||||
|
|
||||||
collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{})
|
collector := createSolanaCollector(&staticRPCClient{}, 100*time.Millisecond, identities, []string{}, votekeys)
|
||||||
watcher := NewCollectorSlotWatcher(collector)
|
watcher := NewCollectorSlotWatcher(collector)
|
||||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
go watcher.WatchSlots(ctx, collector.slotPace)
|
go watcher.WatchSlots(ctx, collector.slotPace)
|
||||||
|
|
||||||
|
// make sure inflation rewards are collected:
|
||||||
|
err := watcher.fetchAndEmitInflationRewards(ctx, staticEpochInfo.Epoch)
|
||||||
|
assert.NoError(t, err)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
firstSlot, lastSlot := getEpochBounds(&staticEpochInfo)
|
firstSlot, lastSlot := getEpochBounds(&staticEpochInfo)
|
||||||
tests := []struct {
|
type testCase struct {
|
||||||
expectedValue float64
|
expectedValue float64
|
||||||
metric prometheus.Gauge
|
metric prometheus.Gauge
|
||||||
}{
|
}
|
||||||
|
tests := []testCase{
|
||||||
{expectedValue: float64(staticEpochInfo.AbsoluteSlot), metric: confirmedSlotHeight},
|
{expectedValue: float64(staticEpochInfo.AbsoluteSlot), metric: confirmedSlotHeight},
|
||||||
{expectedValue: float64(staticEpochInfo.TransactionCount), metric: totalTransactionsTotal},
|
{expectedValue: float64(staticEpochInfo.TransactionCount), metric: totalTransactionsTotal},
|
||||||
{expectedValue: float64(staticEpochInfo.Epoch), metric: currentEpochNumber},
|
{expectedValue: float64(staticEpochInfo.Epoch), metric: currentEpochNumber},
|
||||||
|
@ -111,6 +117,16 @@ func TestSolanaCollector_WatchSlots_Static(t *testing.T) {
|
||||||
{expectedValue: float64(lastSlot), metric: epochLastSlot},
|
{expectedValue: float64(lastSlot), metric: epochLastSlot},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add inflation reward tests:
|
||||||
|
for i, rewardInfo := range staticInflationRewards {
|
||||||
|
epoch := fmt.Sprintf("%v", staticEpochInfo.Epoch)
|
||||||
|
test := testCase{
|
||||||
|
expectedValue: float64(rewardInfo.Amount) / float64(rpc.LamportsInSol),
|
||||||
|
metric: inflationRewards.WithLabelValues(votekeys[i], epoch),
|
||||||
|
}
|
||||||
|
tests = append(tests, test)
|
||||||
|
}
|
||||||
|
|
||||||
for _, testCase := range tests {
|
for _, testCase := range tests {
|
||||||
name := extractName(testCase.metric.Desc())
|
name := extractName(testCase.metric.Desc())
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
@ -145,8 +161,8 @@ func TestSolanaCollector_WatchSlots_Dynamic(t *testing.T) {
|
||||||
|
|
||||||
// create clients:
|
// create clients:
|
||||||
client := newDynamicRPCClient()
|
client := newDynamicRPCClient()
|
||||||
collector := createSolanaCollector(client, 300*time.Millisecond, identities, []string{})
|
collector := createSolanaCollector(client, 300*time.Millisecond, identities, []string{}, votekeys)
|
||||||
watcher := SlotWatcher{client: client}
|
watcher := NewCollectorSlotWatcher(collector)
|
||||||
prometheus.NewPedanticRegistry().MustRegister(collector)
|
prometheus.NewPedanticRegistry().MustRegister(collector)
|
||||||
|
|
||||||
// start client/collector and wait a bit:
|
// start client/collector and wait a bit:
|
||||||
|
|
|
@ -65,6 +65,12 @@ type Provider interface {
|
||||||
|
|
||||||
// GetBalance returns the SOL balance of the account at the provided address
|
// GetBalance returns the SOL balance of the account at the provided address
|
||||||
GetBalance(ctx context.Context, address string) (float64, error)
|
GetBalance(ctx context.Context, address string) (float64, error)
|
||||||
|
|
||||||
|
// GetInflationReward returns the inflation rewards (in lamports) awarded to the given addresses (vote accounts)
|
||||||
|
// during the given epoch.
|
||||||
|
GetInflationReward(
|
||||||
|
ctx context.Context, addresses []string, commitment Commitment, epoch *int64, minContextSlot *int64,
|
||||||
|
) ([]InflationReward, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Commitment) MarshalJSON() ([]byte, error) {
|
func (c Commitment) MarshalJSON() ([]byte, error) {
|
||||||
|
@ -72,6 +78,8 @@ func (c Commitment) MarshalJSON() ([]byte, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// LamportsInSol is the number of lamports in 1 SOL (a billion)
|
||||||
|
LamportsInSol = 1_000_000_000
|
||||||
// CommitmentFinalized level offers the highest level of certainty for a transaction on the Solana blockchain.
|
// CommitmentFinalized level offers the highest level of certainty for a transaction on the Solana blockchain.
|
||||||
// A transaction is considered “Finalized” when it is included in a block that has been confirmed by a
|
// A transaction is considered “Finalized” when it is included in a block that has been confirmed by a
|
||||||
// supermajority of the stake, and at least 31 additional confirmed blocks have been built on top of it.
|
// supermajority of the stake, and at least 31 additional confirmed blocks have been built on top of it.
|
||||||
|
@ -216,5 +224,24 @@ func (c *Client) GetBalance(ctx context.Context, address string) (float64, error
|
||||||
if err := c.getResponse(ctx, "getBalance", []any{address}, &resp); err != nil {
|
if err := c.getResponse(ctx, "getBalance", []any{address}, &resp); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return float64(resp.Result.Value / 1_000_000_000), nil
|
return float64(resp.Result.Value) / float64(LamportsInSol), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetInflationReward(
|
||||||
|
ctx context.Context, addresses []string, commitment Commitment, epoch *int64, minContextSlot *int64,
|
||||||
|
) ([]InflationReward, error) {
|
||||||
|
// format params:
|
||||||
|
config := map[string]any{"commitment": string(commitment)}
|
||||||
|
if epoch != nil {
|
||||||
|
config["epoch"] = *epoch
|
||||||
|
}
|
||||||
|
if minContextSlot != nil {
|
||||||
|
config["minContextSlot"] = *minContextSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp response[[]InflationReward]
|
||||||
|
if err := c.getResponse(ctx, "getInflationReward", []any{addresses, config}, &resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.Result, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
|
|
||||||
type (
|
type (
|
||||||
response[T any] struct {
|
response[T any] struct {
|
||||||
Result T `json:"result"`
|
jsonrpc string
|
||||||
Error rpcError `json:"error"`
|
Result T `json:"result"`
|
||||||
|
Error rpcError `json:"error"`
|
||||||
|
Id int `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
contextualResult[T any] struct {
|
contextualResult[T any] struct {
|
||||||
|
@ -63,6 +65,13 @@ type (
|
||||||
ByIdentity map[string]HostProduction `json:"byIdentity"`
|
ByIdentity map[string]HostProduction `json:"byIdentity"`
|
||||||
Range BlockProductionRange `json:"range"`
|
Range BlockProductionRange `json:"range"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InflationReward struct {
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
EffectiveSlot int64 `json:"effectiveSlot"`
|
||||||
|
Epoch int64 `json:"epoch"`
|
||||||
|
PostBalance int64 `json:"postBalance"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (hp *HostProduction) UnmarshalJSON(data []byte) error {
|
func (hp *HostProduction) UnmarshalJSON(data []byte) error {
|
||||||
|
|
Loading…
Reference in New Issue