12
12
)
13
13
from src .metrics .prometheus .duration_meter import duration_meter
14
14
from src .modules .csm .checkpoint import FrameCheckpointProcessor , FrameCheckpointsIterator , MinStepIsNotReached
15
- from src .modules .csm .log import FramePerfLog
15
+ from src .modules .csm .duties .attestation import calc_performance
16
+ from src .modules .csm .log import FramePerfLog , AttestationsAccumulatorLog
16
17
from src .modules .csm .state import State
17
18
from src .modules .csm .tree import Tree
18
19
from src .modules .csm .types import ReportData , Shares
33
34
)
34
35
from src .utils .blockstamp import build_blockstamp
35
36
from src .utils .cache import global_lru_cache as lru_cache
36
- from src .utils .slot import get_next_non_missed_slot
37
+ from src .utils .slot import get_next_non_missed_slot , get_reference_blockstamp
37
38
from src .utils .web3converter import Web3Converter
38
39
from src .web3py .extensions .lido_validators import NodeOperatorId , StakingModule , ValidatorsByNodeOperator
39
40
from src .web3py .types import Web3
@@ -102,12 +103,12 @@ def build_report(self, blockstamp: ReferenceBlockStamp) -> tuple:
102
103
if (prev_cid is None ) != (prev_root == ZERO_HASH ):
103
104
raise InconsistentData (f"Got inconsistent previous tree data: { prev_root = } { prev_cid = } " )
104
105
105
- distributed , shares , log = self .calculate_distribution (blockstamp )
106
+ distributed , shares , logs = self .calculate_distribution (blockstamp )
106
107
107
108
if distributed != sum (shares .values ()):
108
109
raise InconsistentData (f"Invalid distribution: { sum (shares .values ())= } != { distributed = } " )
109
110
110
- log_cid = self .publish_log (log )
111
+ log_cid = self .publish_log (logs )
111
112
112
113
if not distributed and not shares :
113
114
logger .info ({"msg" : "No shares distributed in the current frame" })
@@ -225,27 +226,64 @@ def collect_data(self, blockstamp: BlockStamp) -> bool:
225
226
226
227
def calculate_distribution (
227
228
self , blockstamp : ReferenceBlockStamp
228
- ) -> tuple [int , defaultdict [NodeOperatorId , int ], FramePerfLog ]:
229
+ ) -> tuple [int , defaultdict [NodeOperatorId , int ], list [ FramePerfLog ] ]:
229
230
"""Computes distribution of fee shares at the given timestamp"""
230
-
231
- network_avg_perf = self .state .get_network_aggr ().perf
232
- threshold = network_avg_perf - self .w3 .csm .oracle .perf_leeway_bp (blockstamp .block_hash ) / TOTAL_BASIS_POINTS
233
231
operators_to_validators = self .module_validators_by_node_operators (blockstamp )
234
232
233
+ distributed = 0
234
+ # Calculate share of each CSM node operator.
235
+ shares = defaultdict [NodeOperatorId , int ](int )
236
+ logs : list [FramePerfLog ] = []
237
+
238
+ converter = self .converter (blockstamp )
239
+ frames = self .state .calc_frames (converter .frame_config .epochs_per_frame )
240
+ for from_epoch , to_epoch in frames :
241
+ frame_blockstamp = blockstamp
242
+ frame_ref_slot = converter .get_epoch_first_slot (to_epoch )
243
+ if blockstamp .slot_number != frame_ref_slot :
244
+ frame_blockstamp = get_reference_blockstamp (
245
+ cc = self .w3 .cc ,
246
+ ref_slot = converter .get_epoch_first_slot (to_epoch ),
247
+ ref_epoch = to_epoch ,
248
+ last_finalized_slot_number = blockstamp .slot_number ,
249
+ )
250
+ distributed_in_frame , shares_in_frame , log = self ._calculate_distribution_in_frame (
251
+ frame_blockstamp , operators_to_validators , from_epoch , to_epoch , distributed
252
+ )
253
+ distributed += distributed_in_frame
254
+ for no_id , share in shares_in_frame .items ():
255
+ shares [no_id ] += share
256
+ logs .append (log )
257
+
258
+ return distributed , shares , logs
259
+
260
+ def _calculate_distribution_in_frame (
261
+ self ,
262
+ blockstamp : ReferenceBlockStamp ,
263
+ operators_to_validators : ValidatorsByNodeOperator ,
264
+ from_epoch : EpochNumber ,
265
+ to_epoch : EpochNumber ,
266
+ distributed : int ,
267
+ ):
268
+ network_perf = self .state .calc_network_perf (from_epoch , to_epoch )
269
+ threshold = network_perf - self .w3 .csm .oracle .perf_leeway_bp (blockstamp .block_hash ) / TOTAL_BASIS_POINTS
270
+
235
271
# Build the map of the current distribution operators.
236
272
distribution : dict [NodeOperatorId , int ] = defaultdict (int )
237
273
stuck_operators = self .stuck_operators (blockstamp )
238
- log = FramePerfLog (blockstamp , self . state . frame , threshold )
274
+ log = FramePerfLog (blockstamp , ( from_epoch , to_epoch ) , threshold )
239
275
240
276
for (_ , no_id ), validators in operators_to_validators .items ():
241
277
if no_id in stuck_operators :
242
278
log .operators [no_id ].stuck = True
243
279
continue
244
280
245
281
for v in validators :
246
- aggr = self .state .data .get (ValidatorIndex (int (v .index )))
282
+ missed = self .state .count_missed (ValidatorIndex (int (v .index )), from_epoch , to_epoch )
283
+ included = self .state .count_included (ValidatorIndex (int (v .index )), from_epoch , to_epoch )
284
+ assigned = missed + included
247
285
248
- if aggr is None :
286
+ if not assigned :
249
287
# It's possible that the validator is not assigned to any duty, hence it's performance
250
288
# is not presented in the aggregates (e.g. exited, pending for activation etc).
251
289
continue
@@ -256,23 +294,23 @@ def calculate_distribution(
256
294
log .operators [no_id ].validators [v .index ].slashed = True
257
295
continue
258
296
259
- if aggr .perf > threshold :
297
+ perf = calc_performance (included , missed )
298
+ if perf > threshold :
260
299
# Count of assigned attestations used as a metrics of time
261
300
# the validator was active in the current frame.
262
- distribution [no_id ] += aggr . assigned
301
+ distribution [no_id ] += assigned
263
302
264
- log .operators [no_id ].validators [v .index ].perf = aggr
303
+ log .operators [no_id ].validators [v .index ].perf = AttestationsAccumulatorLog ( assigned , included )
265
304
266
305
# Calculate share of each CSM node operator.
267
306
shares = defaultdict [NodeOperatorId , int ](int )
268
307
total = sum (p for p in distribution .values ())
308
+ to_distribute = self .w3 .csm .fee_distributor .shares_to_distribute (blockstamp .block_hash ) - distributed
309
+ log .distributable = to_distribute
269
310
270
311
if not total :
271
312
return 0 , shares , log
272
313
273
- to_distribute = self .w3 .csm .fee_distributor .shares_to_distribute (blockstamp .block_hash )
274
- log .distributable = to_distribute
275
-
276
314
for no_id , no_share in distribution .items ():
277
315
if no_share :
278
316
shares [no_id ] = to_distribute * no_share // total
@@ -343,9 +381,9 @@ def publish_tree(self, tree: Tree) -> CID:
343
381
logger .info ({"msg" : "Tree dump uploaded to IPFS" , "cid" : repr (tree_cid )})
344
382
return tree_cid
345
383
346
- def publish_log (self , log : FramePerfLog ) -> CID :
347
- log_cid = self .w3 .ipfs .publish (log .encode ())
348
- logger .info ({"msg" : "Frame log uploaded to IPFS" , "cid" : repr (log_cid )})
384
+ def publish_log (self , logs : list [ FramePerfLog ] ) -> CID :
385
+ log_cid = self .w3 .ipfs .publish (FramePerfLog .encode (logs ))
386
+ logger .info ({"msg" : "Frame(s) log uploaded to IPFS" , "cid" : repr (log_cid )})
349
387
return log_cid
350
388
351
389
@lru_cache (maxsize = 1 )
0 commit comments