@gabrielhicks/solv 5.6.6 → 5.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ from pprint import pprint
2
+ import json
3
+ import numpy
4
+ import time
5
+
6
+
7
+ class ValidatorConfig:
8
+ def __init__(self,
9
+ validator_name: str,
10
+ secrets_path: str,
11
+ local_rpc_address: str,
12
+ remote_rpc_address: str,
13
+ cluster_environment: str,
14
+ debug_mode: bool):
15
+ self.validator_name = validator_name
16
+ self.secrets_path = secrets_path
17
+ self.local_rpc_address = local_rpc_address
18
+ self.remote_rpc_address = remote_rpc_address
19
+ self.cluster_environment = cluster_environment
20
+ self.debug_mode = debug_mode
21
+
22
+
23
+ class JsonEncoder(json.JSONEncoder):
24
+ """ Special json encoder for numpy types """
25
+ def default(self, obj):
26
+ if isinstance(obj, (numpy.int_, numpy.intc, numpy.intp, numpy.int8,
27
+ numpy.int16, numpy.int32, numpy.int64, numpy.uint8,
28
+ numpy.uint16, numpy.uint32, numpy.uint64)):
29
+ return int(obj)
30
+ elif isinstance(obj, (numpy.float_, numpy.float16, numpy.float32,
31
+ numpy.float64)):
32
+ return float(obj)
33
+ elif isinstance(obj, (numpy.ndarray,)):
34
+ return obj.tolist()
35
+ return json.JSONEncoder.default(self, obj)
36
+
37
+
38
+ def debug(config: ValidatorConfig, data):
39
+ if config.debug_mode:
40
+ pprint(data)
41
+
42
+
43
+ def print_json(data):
44
+ print(json.dumps(data, cls=JsonEncoder))
45
+
46
+
47
+ def measurement_from_fields(name, data, tags, config, legacy_tags=None):
48
+ if legacy_tags is None:
49
+ legacy_tags = {}
50
+ data.update({"cluster_environment": config.cluster_environment})
51
+ measurement = {
52
+ "measurement": name,
53
+ "time": round(time.time() * 1000),
54
+ "monitoring_version": "3.2.0",
55
+ "cluster_environment": config.cluster_environment,
56
+ "fields": data,
57
+ "tags": tags
58
+ }
59
+
60
+ measurement.update(legacy_tags)
61
+
62
+ return measurement
63
+
@@ -0,0 +1,353 @@
1
+ import time
2
+ import solana_rpc as rpc
3
+ from common import debug
4
+ from common import ValidatorConfig
5
+ import statistics
6
+ import numpy as np
7
+ from common import measurement_from_fields
8
+
9
+
10
+ def get_metrics_from_vote_account_item(item):
11
+ return {
12
+ 'epoch_number': item['epochCredits'][-1][0],
13
+ 'credits_epoch': item['epochCredits'][-1][1],
14
+ 'credits_previous_epoch': item['epochCredits'][-1][2],
15
+ 'activated_stake': item['activatedStake'],
16
+ 'credits_epoch_delta': item['epochCredits'][-1][1] - item['epochCredits'][-1][2],
17
+ 'commission': item['commission']
18
+ }
19
+
20
+
21
+ def find_item_in_vote_accounts_section(identity_account_pubkey, section_parent, section_name):
22
+ if section_name in section_parent:
23
+ section = section_parent[section_name]
24
+ for item in section:
25
+ if item['nodePubkey'] == identity_account_pubkey:
26
+ return get_metrics_from_vote_account_item(item)
27
+
28
+ return None
29
+
30
+
31
+ def get_vote_account_metrics(vote_accounts_data, identity_account_pubkey):
32
+ """
33
+ get vote metrics from vote account
34
+ :return:
35
+ voting_status: 0 if validator not found in voting accounts
36
+ voting_status: 1 if validator is current
37
+ voting_status: 2 if validator is delinquent
38
+
39
+ """
40
+ result = find_item_in_vote_accounts_section(identity_account_pubkey, vote_accounts_data, 'current')
41
+ if result is not None:
42
+ result.update({'voting_status': 1})
43
+ else:
44
+ result = find_item_in_vote_accounts_section(identity_account_pubkey, vote_accounts_data, 'delinquent')
45
+ if result is not None:
46
+ result.update({'voting_status': 2})
47
+ else:
48
+ result = {'voting_status': 0}
49
+ return result
50
+
51
+
52
+ def get_leader_schedule_metrics(leader_schedule_data, identity_account_pubkey):
53
+ """
54
+ get metrics about leader slots
55
+ """
56
+ if identity_account_pubkey in leader_schedule_data:
57
+ return {"leader_slots_this_epoch": len(leader_schedule_data[identity_account_pubkey])}
58
+ else:
59
+ return {"leader_slots_this_epoch": 0}
60
+
61
+
62
+ def get_block_production_metrics(block_production_data, identity_account_pubkey):
63
+ try:
64
+ item = block_production_data['value']['byIdentity'][identity_account_pubkey]
65
+ return {
66
+ "slots_done": item[0],
67
+ "slots_skipped": item[0] - item[1],
68
+ "blocks_produced": item[1]
69
+
70
+ }
71
+ except:
72
+ return {"slots_done": 0, "slots_skipped": 0, "blocks_produced": 0}
73
+
74
+
75
+ def get_block_production_cli_metrics(block_production_data_cli, identity_account_pubkey: str):
76
+ if 'leaders' in block_production_data_cli:
77
+ leaders = block_production_data_cli['leaders']
78
+ skip_rate = []
79
+ my_skip_rate = 0
80
+ for leader in leaders:
81
+ leader_slots = leader.get('leaderSlots', 0)
82
+ if leader_slots > 0:
83
+ current_skip_rate = leader.get('skippedSlots', 0) / leader_slots
84
+ skip_rate.append(current_skip_rate)
85
+ if leader['identityPubkey'] == identity_account_pubkey:
86
+ my_skip_rate = current_skip_rate
87
+
88
+ result = {
89
+ 'leader_skip_rate': my_skip_rate,
90
+ 'cluster_min_leader_skip_rate': min(skip_rate),
91
+ 'cluster_max_leader_skip_rate': max(skip_rate),
92
+ 'cluster_mean_leader_skip_rate': statistics.mean(skip_rate),
93
+ 'cluster_median_leader_skip_rate': statistics.median(skip_rate),
94
+ }
95
+ else:
96
+ result = {}
97
+
98
+ return result
99
+
100
+
101
+ def get_performance_metrics(performance_sample_data, epoch_info_data, leader_schedule_by_identity):
102
+ if len(performance_sample_data) > 0:
103
+ sample = performance_sample_data[0]
104
+ if sample['numSlots'] > 0:
105
+ mid_slot_time = sample['samplePeriodSecs'] / sample['numSlots']
106
+ else:
107
+ mid_slot_time = 0
108
+ current_slot_index = epoch_info_data['slotIndex']
109
+ remaining_time = (epoch_info_data["slotsInEpoch"] - current_slot_index) * mid_slot_time
110
+ epoch_end_time = round(time.time()) + remaining_time
111
+ time_until_next_slot = -1
112
+ if leader_schedule_by_identity is not None:
113
+ for slot in leader_schedule_by_identity:
114
+ if current_slot_index < slot:
115
+ next_slot = slot
116
+ time_until_next_slot = (next_slot - current_slot_index) * mid_slot_time
117
+ break
118
+ else:
119
+ time_until_next_slot = None
120
+
121
+ result = {
122
+ "epoch_endtime": epoch_end_time,
123
+ "epoch_remaining_sec": remaining_time
124
+ }
125
+
126
+ if time_until_next_slot is not None:
127
+ result.update({"time_until_next_slot": time_until_next_slot})
128
+ else:
129
+ result = {}
130
+
131
+ return result
132
+
133
+
134
+ def get_balance_metric(balance_data, key: str):
135
+ if 'value' in balance_data:
136
+ result = {key: balance_data['value']}
137
+ else:
138
+ result = {}
139
+
140
+ return result
141
+
142
+
143
+ def get_solana_version_metric(solana_version_data):
144
+ if solana_version_data is not None:
145
+ if 'solana-core' in solana_version_data:
146
+ return {'solana_version': solana_version_data['solana-core']}
147
+
148
+ return {}
149
+
150
+
151
+ def get_validators_metric(validators, identity_account_pubkey):
152
+ if validators is not None:
153
+ epoch_credits_l = []
154
+ last_vote_l = []
155
+ root_slot_l = []
156
+ current_last_vote = -1
157
+ current_root_slot = -1
158
+
159
+ for v in validators:
160
+ if not v['delinquent']:
161
+ epoch_credits_l.append(v['epochCredits'])
162
+ last_vote_l.append(v['lastVote'])
163
+ root_slot_l.append(v['rootSlot'])
164
+ if identity_account_pubkey == v['identityPubkey']:
165
+ current_last_vote = v['lastVote']
166
+ current_root_slot = v['rootSlot']
167
+
168
+ epoch_credits = np.array(epoch_credits_l, dtype=np.int32)
169
+ last_vote = np.array(last_vote_l, dtype=np.int32)
170
+ root_slot = np.array(root_slot_l, dtype=np.int32)
171
+
172
+ last_vote = last_vote[last_vote > 0]
173
+ root_slot = root_slot[root_slot > 0]
174
+
175
+ cluster_max_last_vote = np.amax(last_vote)
176
+ cluster_min_last_vote = np.amin(last_vote)
177
+ cluster_mean_last_vote = abs((last_vote - cluster_max_last_vote).mean())
178
+ cluster_median_last_vote = abs(np.median(last_vote - cluster_max_last_vote))
179
+
180
+ cluster_max_root_slot = np.amax(root_slot)
181
+ cluster_min_root_slot = np.amin(root_slot)
182
+ cluster_mean_root_slot = abs((root_slot - cluster_max_root_slot).mean())
183
+ cluster_median_root_slot = abs(np.median(root_slot - cluster_max_root_slot))
184
+
185
+ result = {
186
+ 'cluster_mean_epoch_credits': epoch_credits.mean(),
187
+ 'cluster_min_epoch_credits': np.amin(epoch_credits),
188
+ 'cluster_max_epoch_credits': np.amax(epoch_credits),
189
+ 'cluster_median_epoch_credits': np.median(epoch_credits),
190
+
191
+ 'cluster_max_last_vote': cluster_max_last_vote,
192
+ 'cluster_min_last_vote_v2': cluster_min_last_vote,
193
+ 'cluster_mean_last_vote_v2': cluster_mean_last_vote,
194
+ 'cluster_median_last_vote': cluster_median_last_vote,
195
+ 'current_last_vote': current_last_vote,
196
+
197
+ 'cluster_max_root_slot': cluster_max_root_slot,
198
+ 'cluster_min_root_slot_v2': cluster_min_root_slot,
199
+ 'cluster_mean_root_slot_v2': cluster_mean_root_slot,
200
+ 'cluster_median_root_slot': cluster_median_root_slot,
201
+ 'current_root_slot': current_root_slot
202
+ }
203
+
204
+ else:
205
+ result = {}
206
+
207
+ return result
208
+
209
+
210
+ def get_current_stake_metric(stake_data):
211
+ active = 0
212
+ activating = 0
213
+ deactivating = 0
214
+ active_cnt = 0
215
+ activating_cnt = 0
216
+ deactivating_cnt = 0
217
+ for item in stake_data:
218
+ if 'activeStake' in item:
219
+ active = active + item.get('activeStake', 0)
220
+ active_cnt = active_cnt + 1
221
+ if 'activatingStake' in item:
222
+ activating = activating + item.get('activatingStake', 0)
223
+ activating_cnt = activating_cnt + 1
224
+ if 'deactivatingStake' in item:
225
+ deactivating = deactivating + item.get('deactivatingStake', 0)
226
+ deactivating_cnt = deactivating_cnt + 1
227
+
228
+ return {
229
+ 'active_stake': active,
230
+ 'activating_stake': activating,
231
+ 'deactivating_stake': deactivating,
232
+ 'stake_holders': len(stake_data),
233
+ 'active_cnt': active_cnt,
234
+ 'activating_cnt': activating_cnt,
235
+ 'deactivating_cnt': deactivating_cnt
236
+ }
237
+
238
+
239
+ def load_data(config: ValidatorConfig):
240
+ identity_account_pubkey = rpc.load_identity_account_pubkey(config)
241
+ vote_account_pubkey = rpc.load_vote_account_pubkey(config)
242
+
243
+ epoch_info_data = rpc.load_epoch_info(config)
244
+ block_production_cli = rpc.load_block_production_cli(config)
245
+ performance_sample_data = rpc.load_recent_performance_sample(config)
246
+ solana_version_data = rpc.load_solana_version(config)
247
+ validators_data = rpc.load_solana_validators(config)
248
+
249
+ default = []
250
+
251
+ identity_account_balance_data = default
252
+ leader_schedule_data = default
253
+ block_production_data = default
254
+
255
+ vote_account_balance_data = default
256
+ vote_accounts_data = default
257
+ stakes_data = default
258
+
259
+ if identity_account_pubkey is not None:
260
+ identity_account_balance_data = rpc.load_identity_account_balance(config, identity_account_pubkey)
261
+ leader_schedule_data = rpc.load_leader_schedule(config, identity_account_pubkey)
262
+ block_production_data = rpc.load_block_production(config, identity_account_pubkey)
263
+
264
+ if vote_account_pubkey is not None:
265
+ vote_account_balance_data = rpc.load_vote_account_balance(config, vote_account_pubkey)
266
+ vote_accounts_data = rpc.load_vote_accounts(config, vote_account_pubkey)
267
+ stakes_data = rpc.load_stakes(config, vote_account_pubkey)
268
+
269
+ result = {
270
+ 'identity_account_pubkey': identity_account_pubkey,
271
+ 'vote_account_pubkey': vote_account_pubkey,
272
+ 'identity_account_balance': identity_account_balance_data,
273
+ 'vote_account_balance': vote_account_balance_data,
274
+ 'epoch_info': epoch_info_data,
275
+ 'leader_schedule': leader_schedule_data,
276
+ 'block_production': block_production_data,
277
+ 'load_block_production_cli': block_production_cli,
278
+ 'vote_accounts': vote_accounts_data,
279
+ 'performance_sample': performance_sample_data,
280
+ 'solana_version_data': solana_version_data,
281
+ 'stakes_data': stakes_data,
282
+ 'validators_data': validators_data,
283
+ 'cpu_model': rpc.load_cpu_model(config)
284
+ }
285
+
286
+ debug(config, str(result))
287
+
288
+ return result
289
+
290
+
291
+ def calculate_influx_fields(data):
292
+ if data is None:
293
+ result = {"validator_status": 0}
294
+ else:
295
+ identity_account_pubkey = data['identity_account_pubkey']
296
+
297
+ vote_account_metrics = get_vote_account_metrics(data['vote_accounts'], identity_account_pubkey)
298
+ leader_schedule_metrics = get_leader_schedule_metrics(data['leader_schedule'], identity_account_pubkey)
299
+ epoch_metrics = data['epoch_info']
300
+ block_production_metrics = get_block_production_metrics(data['block_production'], identity_account_pubkey)
301
+ if identity_account_pubkey in data['leader_schedule']:
302
+ leader_schedule_by_identity = data['leader_schedule'][identity_account_pubkey]
303
+ else:
304
+ leader_schedule_by_identity = None
305
+
306
+ performance_metrics = get_performance_metrics(
307
+ data['performance_sample'], epoch_metrics, leader_schedule_by_identity)
308
+
309
+ result = {"validator_status": 1}
310
+ result.update(vote_account_metrics)
311
+ result.update(leader_schedule_metrics)
312
+ result.update(epoch_metrics)
313
+ result.update(block_production_metrics)
314
+ result.update(performance_metrics)
315
+ result.update(get_balance_metric(data['identity_account_balance'], 'identity_account_balance'))
316
+ result.update(get_balance_metric(data['vote_account_balance'], 'vote_account_balance'))
317
+ result.update(get_current_stake_metric(data['stakes_data']))
318
+ result.update(get_validators_metric(data['validators_data'], identity_account_pubkey))
319
+ result.update(get_block_production_cli_metrics(data['load_block_production_cli'], identity_account_pubkey))
320
+ result.update({"cpu_model": data['cpu_model']})
321
+
322
+ return result
323
+
324
+
325
+ def calculate_output_data(config: ValidatorConfig):
326
+ data = load_data(config)
327
+
328
+ tags = {
329
+ "validator_identity_pubkey": data['identity_account_pubkey'],
330
+ "validator_vote_pubkey": data['vote_account_pubkey'],
331
+ "validator_name": config.validator_name,
332
+ "cluster_environment": config.cluster_environment
333
+ }
334
+
335
+ legacy_tags = {
336
+ "validator_identity_pubkey": data['identity_account_pubkey'],
337
+ "validator_vote_pubkey": data['vote_account_pubkey'],
338
+ "validator_name": config.validator_name,
339
+ }
340
+
341
+ measurement = measurement_from_fields(
342
+ "validators_info",
343
+ calculate_influx_fields(data),
344
+ tags,
345
+ config,
346
+ legacy_tags
347
+ )
348
+ measurement.update({"cpu_model": data['cpu_model']})
349
+ if data is not None and 'solana_version_data' in data:
350
+ measurement.update(get_solana_version_metric(data['solana_version_data']))
351
+
352
+ return measurement
353
+
@@ -0,0 +1,6 @@
1
+ from monitoring_config import config
2
+ from measurement_validator_info import calculate_output_data
3
+ from common import print_json
4
+
5
+ print_json(calculate_output_data(config))
6
+
@@ -0,0 +1,76 @@
1
+ from common import ValidatorConfig
2
+ import subprocess
3
+ import requests
4
+ import json
5
+ from common import debug
6
+
7
+
8
+ def execute_cmd_str(config: ValidatorConfig, cmd: str, convert_to_json: bool, default=None):
9
+ """
10
+ executes shell command and return string result
11
+ :param default:
12
+ :param config:
13
+ :param convert_to_json:
14
+ :param cmd: shell command
15
+ :return: returns string result or None
16
+ """
17
+ try:
18
+ debug(config, cmd)
19
+ result: str = subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL, timeout=10).decode().strip()
20
+
21
+ if convert_to_json:
22
+ result = json.loads(result)
23
+
24
+ debug(config, result)
25
+
26
+ return result
27
+ except:
28
+ return default
29
+
30
+
31
+ def rpc_call(config: ValidatorConfig, address: str, method: str, params, error_result, except_result):
32
+ """
33
+ calls solana rpc (https://docs.solana.com/developing/clients/jsonrpc-api)
34
+ and returns result or default
35
+ :param config:
36
+ :param except_result:
37
+ :param error_result:
38
+ :param address: local or remote rpc server address
39
+ :param method: rpc method
40
+ :param params: rpc call parameters
41
+ :return: result or default
42
+ """
43
+ try:
44
+ json_request = {
45
+ "jsonrpc": "2.0",
46
+ "id": 1,
47
+ "method": method,
48
+ "params": params
49
+ }
50
+ debug(config, json_request)
51
+ debug(config, address)
52
+
53
+ json_response = requests.post(address, json=json_request).json()
54
+ if 'result' not in json_response:
55
+ result = error_result
56
+ else:
57
+ result = json_response['result']
58
+ except:
59
+ result = except_result
60
+
61
+ debug(config, result)
62
+
63
+ return result
64
+
65
+
66
+ def smart_rpc_call(config: ValidatorConfig, method: str, params, default_result):
67
+ """
68
+ tries to call local rpc, if it fails tries to call remote rpc
69
+ """
70
+ result = rpc_call(config, config.local_rpc_address, method, params, None, None)
71
+
72
+ if result is None:
73
+ result = rpc_call(config, config.remote_rpc_address, method, params, default_result, default_result)
74
+
75
+ return result
76
+
@@ -0,0 +1,202 @@
1
+ from common import ValidatorConfig
2
+ from typing import Optional
3
+ from common import debug
4
+ from request_utils import execute_cmd_str, smart_rpc_call, rpc_call
5
+
6
+
7
+ def load_identity_account_pubkey(config: ValidatorConfig) -> Optional[str]:
8
+ """
9
+ loads validator identity account pubkey
10
+ :param config: Validator Configuration
11
+ :return: returns validator identity pubkey or None
12
+ """
13
+ identity_cmd = f'solana address -u localhost --keypair ' + config.secrets_path + '/validator-keypair.json'
14
+ debug(config, identity_cmd)
15
+ return execute_cmd_str(config, identity_cmd, convert_to_json=False)
16
+
17
+
18
+ def load_vote_account_pubkey(config: ValidatorConfig) -> Optional[str]:
19
+ """
20
+ loads vote account pubkey
21
+ :param config: Validator Configuration
22
+ :return: returns vote account pubkey or None
23
+ """
24
+ vote_pubkey_cmd = f'solana address -u localhost --keypair ' + config.secrets_path + '/vote-account-keypair.json'
25
+ debug(config, vote_pubkey_cmd)
26
+ return execute_cmd_str(config, vote_pubkey_cmd, convert_to_json=False)
27
+
28
+
29
+ def load_vote_account_balance(config: ValidatorConfig, vote_account_pubkey: str):
30
+ """
31
+ loads vote account balance
32
+ https://docs.solana.com/developing/clients/jsonrpc-api#getbalance
33
+ """
34
+ return smart_rpc_call(config, "getBalance", [vote_account_pubkey], {})
35
+
36
+
37
+ def load_identity_account_balance(config: ValidatorConfig, identity_account_pubkey: str):
38
+ """
39
+ loads identity account balance
40
+ https://docs.solana.com/developing/clients/jsonrpc-api#getbalance
41
+ """
42
+ return smart_rpc_call(config, "getBalance", [identity_account_pubkey], {})
43
+
44
+
45
+ def load_epoch_info(config: ValidatorConfig):
46
+ """
47
+ loads epoch info
48
+ https://docs.solana.com/developing/clients/jsonrpc-api#getepochinfo
49
+ """
50
+ return smart_rpc_call(config, "getEpochInfo", [], {})
51
+
52
+
53
+ def load_leader_schedule(config: ValidatorConfig, identity_account_pubkey: str):
54
+ """
55
+ loads leader schedule
56
+ https://docs.solana.com/developing/clients/jsonrpc-api#getleaderschedule
57
+ """
58
+ params = [
59
+ None,
60
+ {
61
+ 'identity': identity_account_pubkey
62
+ }
63
+ ]
64
+ return smart_rpc_call(config, "getLeaderSchedule", params, {})
65
+
66
+
67
+ def load_block_production(config: ValidatorConfig, identity_account_pubkey: str):
68
+ """
69
+ loads block production
70
+ https://docs.solana.com/developing/clients/jsonrpc-api#getblockproduction
71
+ """
72
+ params = [
73
+ {
74
+ 'identity': identity_account_pubkey
75
+ }
76
+ ]
77
+ return smart_rpc_call(config, "getBlockProduction", params, {})
78
+
79
+
80
+ def load_block_production_cli(config: ValidatorConfig):
81
+ cmd = f'solana block-production -u l --output json-compact'
82
+ return execute_cmd_str(config, cmd, convert_to_json=True, default={})
83
+
84
+
85
+ def load_vote_accounts(config: ValidatorConfig, vote_account_pubkey: str):
86
+ """
87
+ loads block production
88
+ https://docs.solana.com/developing/clients/jsonrpc-api#getvoteaccounts
89
+ """
90
+ params = [
91
+ {
92
+ 'votePubkey': vote_account_pubkey
93
+ }
94
+ ]
95
+ return smart_rpc_call(config, "getVoteAccounts", params, {})
96
+
97
+
98
+ def load_recent_performance_sample(config: ValidatorConfig):
99
+ """
100
+ loads recent performance sample
101
+ https://docs.solana.com/developing/clients/jsonrpc-api#getrecentperformancesamples
102
+ """
103
+ params = [1]
104
+ return rpc_call(config, config.remote_rpc_address, "getRecentPerformanceSamples", params, [], [])
105
+
106
+
107
+ def load_solana_version(config: ValidatorConfig):
108
+ """
109
+ loads solana version
110
+ https://docs.solana.com/developing/clients/jsonrpc-api#getversion
111
+ """
112
+ return rpc_call(config, config.local_rpc_address, "getVersion", [], [], [])
113
+
114
+
115
+ def load_stake_account_rewards(config: ValidatorConfig, stake_account):
116
+ cmd = f'solana stake-account ' + stake_account + ' --num-rewards-epochs=1 --with-rewards --output json-compact'
117
+ return execute_cmd_str(config, cmd, convert_to_json=True)
118
+
119
+
120
+ def load_solana_validators(config: ValidatorConfig):
121
+ cmd = f'solana validators -ul --output json-compact'
122
+ data = execute_cmd_str(config, cmd, convert_to_json=True)
123
+
124
+ if (data is not None) and ('validators' in data):
125
+ return data['validators']
126
+ else:
127
+ return None
128
+
129
+
130
+ def load_stakes(config: ValidatorConfig, vote_account):
131
+ cmd = f'solana stakes ' + vote_account + ' --output json-compact'
132
+ return execute_cmd_str(config, cmd, convert_to_json=True, default=[])
133
+
134
+
135
+ def load_block_time(config: ValidatorConfig, block):
136
+ """
137
+ loads solana version
138
+ https://docs.solana.com/developing/clients/jsonrpc-api#getblocktime
139
+ """
140
+ params = [block]
141
+ return rpc_call(config, config.local_rpc_address, "getBlockTime", params, None, None)
142
+
143
+ # cmd = f'solana block-time -u l ' + str(block) + ' --output json-compact'
144
+ # return execute_cmd_str(cmd, convert_to_json=True)
145
+
146
+
147
+ def try_to_load_current_block_info(config: ValidatorConfig):
148
+ epoch_info_data = load_epoch_info(config)
149
+
150
+ if epoch_info_data is not None:
151
+ slot_index = epoch_info_data['slotIndex']
152
+ absolute_slot = epoch_info_data['absoluteSlot']
153
+
154
+ block_time_data = load_block_time(config, absolute_slot)
155
+
156
+ if block_time_data is not None:
157
+ return {
158
+ 'slot_index': slot_index,
159
+ 'absolute_block': absolute_slot,
160
+ 'block_time': block_time_data['timestamp']
161
+ }
162
+
163
+ return None
164
+
165
+
166
+ def load_current_block_info(config: ValidatorConfig):
167
+ result = None
168
+ max_tries = 10
169
+ current_try = 0
170
+ while result is None and current_try < max_tries:
171
+ result = try_to_load_current_block_info(config)
172
+ current_try = current_try + 1
173
+
174
+ return result
175
+
176
+
177
+ def load_cpu_model(config: ValidatorConfig):
178
+ cmd = 'cat /proc/cpuinfo | grep name| uniq'
179
+ cpu_info = execute_cmd_str(config, cmd, False).split(":")
180
+ cpu_model = cpu_info[1].strip()
181
+
182
+ if cpu_model is not None:
183
+ return cpu_model
184
+ else:
185
+ return 'Unknown'
186
+
187
+
188
+ def load_solana_validators_full(config: ValidatorConfig):
189
+ cmd = f'solana validators -ul --output json-compact'
190
+ return execute_cmd_str(config, cmd, convert_to_json=True)
191
+
192
+
193
+ def load_solana_validators_info(config: ValidatorConfig):
194
+ cmd = f'solana validator-info get --url ' + config.remote_rpc_address + ' --output json-compact'
195
+ data = execute_cmd_str(config, cmd, convert_to_json=True)
196
+ return data
197
+
198
+
199
+ def load_solana_gossip(config: ValidatorConfig):
200
+ cmd = f'solana gossip -ul --output json-compact'
201
+ return execute_cmd_str(config, cmd, convert_to_json=True)
202
+