Skip to content

Commit fcbbdbf

Browse files
authored
Merge pull request #39 from kilnfi/feat/pool-drep-check
feat: implement pool owner DRep registration monitoring
2 parents 026db7f + c45d07d commit fcbbdbf

File tree

11 files changed

+544
-103
lines changed

11 files changed

+544
-103
lines changed

.golangci.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ linters:
4343
- usestdlibvars
4444
- whitespace
4545
- wrapcheck
46-
- tagliatelle
4746
- sqlclosecheck
4847
- promlinter
4948
- prealloc

internal/blockfrost/blockfrost.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,20 @@ type Client interface {
2323
GetGenesisInfo(ctx context.Context) (blockfrost.GenesisBlock, error)
2424
GetAllPools(ctx context.Context) ([]string, error)
2525
GetNetworkInfo(ctx context.Context) (blockfrost.NetworkInfo, error)
26+
GetAccountInfo(ctx context.Context, stakeAddress string) (Account, error)
27+
}
28+
29+
// TODO: remove it when the blockfrost-go library will be updated and return the DrepID
30+
type Account struct {
31+
StakeAddress string `json:"stake_address"`
32+
Active bool `json:"active"`
33+
ActiveEpoch *int64 `json:"active_epoch"`
34+
ControlledAmount string `json:"controlled_amount"`
35+
RewardsSum string `json:"rewards_sum"`
36+
WithdrawalsSum string `json:"withdrawals_sum"`
37+
ReservesSum string `json:"reserves_sum"`
38+
TreasurySum string `json:"treasury_sum"`
39+
WithdrawableAmount string `json:"withdrawable_amount"`
40+
PoolID *string `json:"pool_id"`
41+
DrepID *string `json:"drep_id"`
2642
}

internal/blockfrost/blockfrostapi/blockfrost.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package blockfrostapi
22

33
import (
44
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
58
"net/http"
9+
"net/url"
610
"time"
711

812
"github.com/blockfrost/blockfrost-go"
@@ -11,6 +15,8 @@ import (
1115

1216
type Client struct {
1317
blockfrost blockfrost.APIClient
18+
apiURL string
19+
projectID string
1420
}
1521

1622
var _ bf.Client = (*Client)(nil)
@@ -34,6 +40,8 @@ func NewClient(opts ClientOptions) *Client {
3440
},
3541
},
3642
),
43+
apiURL: opts.Server,
44+
projectID: opts.ProjectID,
3745
}
3846
}
3947

@@ -173,3 +181,37 @@ func (c *Client) GetAllPools(ctx context.Context) ([]string, error) {
173181
func (c *Client) GetNetworkInfo(ctx context.Context) (blockfrost.NetworkInfo, error) {
174182
return c.blockfrost.Network(ctx)
175183
}
184+
185+
func (c *Client) GetAccountInfo(ctx context.Context, address string) (bf.Account, error) {
186+
account := bf.Account{}
187+
url, err := url.JoinPath(c.apiURL, "accounts", address)
188+
if err != nil {
189+
return account, fmt.Errorf("failed to join URL path to get account details: %w", err)
190+
}
191+
192+
cctx, cancel := context.WithTimeout(ctx, 5*time.Second)
193+
defer cancel()
194+
195+
req, err := http.NewRequestWithContext(cctx, http.MethodGet, url, nil)
196+
if err != nil {
197+
return account, fmt.Errorf("failed to create request to get account details: %w", err)
198+
}
199+
200+
req.Header.Set("Project_id", c.projectID)
201+
resp, err := http.DefaultClient.Do(req)
202+
if err != nil {
203+
return account, fmt.Errorf("failed to send request to get account details: %w", err)
204+
}
205+
defer resp.Body.Close()
206+
207+
body, err := io.ReadAll(resp.Body)
208+
if err != nil {
209+
return account, fmt.Errorf("failed to read response body to get account details: %w", err)
210+
}
211+
212+
if err = json.Unmarshal(body, &account); err != nil {
213+
return account, fmt.Errorf("failed to unmarshal account info: %w", err)
214+
}
215+
216+
return account, nil
217+
}

internal/blockfrost/mocks/mock_Client.go

Lines changed: 61 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cardano/cardanocli/mocks/mock_CommandExecutor.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/cardano/mocks/mock_CardanoClient.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/metrics/metrics.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type Collection struct {
1919
RelaysPerPool *prometheus.GaugeVec
2020
PoolsPledgeMet *prometheus.GaugeVec
2121
PoolsSaturationLevel *prometheus.GaugeVec
22+
PoolsDRepRegistered *prometheus.GaugeVec
2223
MonitoredValidatorsCount *prometheus.GaugeVec
2324
MissedBlocks *prometheus.CounterVec
2425
ConsecutiveMissedBlocks *prometheus.GaugeVec
@@ -127,6 +128,14 @@ func NewCollection() *Collection {
127128
},
128129
[]string{"pool_name", "pool_id", "pool_instance"},
129130
),
131+
PoolsDRepRegistered: prometheus.NewGaugeVec(
132+
prometheus.GaugeOpts{
133+
Namespace: "cardano_validator_watcher",
134+
Name: "pool_drep_registered",
135+
Help: "Whether the pool owner is registered to a DRep (0 or 1)",
136+
},
137+
[]string{"pool_name", "pool_id", "pool_instance"},
138+
),
130139
MonitoredValidatorsCount: prometheus.NewGaugeVec(
131140
prometheus.GaugeOpts{
132141
Namespace: "cardano_validator_watcher",
@@ -216,6 +225,7 @@ func (m *Collection) MustRegister(reg prometheus.Registerer) {
216225
reg.MustRegister(m.NextEpochStartTime)
217226
reg.MustRegister(m.PoolsPledgeMet)
218227
reg.MustRegister(m.PoolsSaturationLevel)
228+
reg.MustRegister(m.PoolsDRepRegistered)
219229
reg.MustRegister(m.MonitoredValidatorsCount)
220230
reg.MustRegister(m.MissedBlocks)
221231
reg.MustRegister(m.ConsecutiveMissedBlocks)

internal/slotleader/mocks/mock_SlotLeader.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/watcher/watcher_block_state_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"testing"
77
"time"
88

9-
"github.com/DATA-DOG/go-sqlmock"
9+
sqlmock "github.com/DATA-DOG/go-sqlmock"
1010
"github.com/blockfrost/blockfrost-go"
1111
"github.com/stretchr/testify/mock"
1212
"github.com/stretchr/testify/require"

internal/watcher/watcher_pool.go

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type PoolWatcher struct {
2929
poolstats pools.PoolStats
3030
healthStore *HealthStore
3131
cache *ristretto.Cache[string, interface{}]
32+
cacheTTL time.Duration
3233
opts PoolWatcherOptions
3334
}
3435

@@ -65,6 +66,7 @@ func NewPoolWatcher(
6566
poolstats: pools.GetPoolStats(),
6667
healthStore: healthStore,
6768
cache: cache,
69+
cacheTTL: 2 * opts.RefreshInterval,
6870
opts: opts,
6971
}, nil
7072
}
@@ -160,6 +162,17 @@ func (w *PoolWatcher) fetch(ctx context.Context) error {
160162
return fmt.Errorf("unable to retrieve relays for pool '%s': %w", pool.ID, err)
161163
}
162164
w.metrics.RelaysPerPool.WithLabelValues(*poolMetadata.Ticker, pool.ID, pool.Instance).Set(float64(len(poolRelays)))
165+
166+
// check if a pool owner is registered to a DRep
167+
poolAccountInfo, err := w.getAccountInfo(ctx, poolInfo.RewardAccount)
168+
if err != nil {
169+
return fmt.Errorf("unable to retrieve account info for pool '%s': %w", pool.ID, err)
170+
}
171+
if poolAccountInfo.DrepID != nil {
172+
w.metrics.PoolsDRepRegistered.WithLabelValues(pool.Name, pool.ID, pool.Instance).Set(1)
173+
} else {
174+
w.metrics.PoolsDRepRegistered.WithLabelValues(pool.Name, pool.ID, pool.Instance).Set(0)
175+
}
163176
}
164177

165178
return nil
@@ -175,7 +188,7 @@ func (w *PoolWatcher) getPoolMetadata(ctx context.Context, PoolID string) (bfAPI
175188
if err != nil {
176189
return metadata, fmt.Errorf("unable to retrieve metadata for pool '%s': %w", PoolID, err)
177190
}
178-
w.cache.SetWithTTL(PoolID+"_metadata", poolMetadata, 1, 2*w.opts.RefreshInterval)
191+
w.cache.SetWithTTL(PoolID+"_metadata", poolMetadata, 1, w.cacheTTL)
179192
w.cache.Wait()
180193
}
181194
metadata = poolMetadata.(bfAPI.PoolMetadata)
@@ -193,7 +206,7 @@ func (w *PoolWatcher) getPoolRelays(ctx context.Context, PoolID string) ([]bfAPI
193206
if err != nil {
194207
return relays, fmt.Errorf("unable to retrieve relays for pool '%s': %w", PoolID, err)
195208
}
196-
w.cache.SetWithTTL(PoolID+"_relays", poolRelays, 1, 2*w.opts.RefreshInterval)
209+
w.cache.SetWithTTL(PoolID+"_relays", poolRelays, 1, w.cacheTTL)
197210
w.cache.Wait()
198211
}
199212
relays = poolRelays.([]bfAPI.PoolRelay)
@@ -211,10 +224,27 @@ func (w *PoolWatcher) getPoolInfo(ctx context.Context, PoolID string) (bfAPI.Poo
211224
if err != nil {
212225
return pool, fmt.Errorf("unable to retrieve info for pool '%s': %w", PoolID, err)
213226
}
214-
w.cache.SetWithTTL(PoolID+"_info", poolInfo, 1, 2*w.opts.RefreshInterval)
227+
w.cache.SetWithTTL(PoolID+"_info", poolInfo, 1, w.cacheTTL)
215228
w.cache.Wait()
216229
}
217230
pool = poolInfo.(bfAPI.Pool)
218231

219232
return pool, nil
220233
}
234+
235+
func (w *PoolWatcher) getAccountInfo(ctx context.Context, address string) (blockfrost.Account, error) {
236+
var err error
237+
var account blockfrost.Account
238+
239+
accountInfo, ok := w.cache.Get(address + "_info")
240+
if !ok {
241+
accountInfo, err = w.blockfrost.GetAccountInfo(ctx, address)
242+
if err != nil {
243+
return blockfrost.Account{}, fmt.Errorf("unable to retrieve info for address '%s': %w", address, err)
244+
}
245+
w.cache.SetWithTTL(address+"_info", accountInfo, 1, w.cacheTTL)
246+
w.cache.Wait()
247+
}
248+
account = accountInfo.(blockfrost.Account)
249+
return account, nil
250+
}

0 commit comments

Comments
 (0)