23
23
from bytestring_splitter import BytestringSplitter , VariableLengthBytestring
24
24
from constant_sorrow .constants import NOT_SIGNED , UNKNOWN_KFRAG
25
25
from twisted .logger import Logger
26
- from typing import Generator , List , Set
26
+ from typing import Generator , List , Set , Optional
27
27
from umbral .keys import UmbralPublicKey
28
28
from umbral .kfrags import KFrag
29
29
@@ -381,7 +381,7 @@ def consider_arrangement(self, network_middleware, ursula, arrangement) -> bool:
381
381
382
382
def make_arrangements (self ,
383
383
network_middleware : RestMiddleware ,
384
- handpicked_ursulas : Set [Ursula ] = None ,
384
+ handpicked_ursulas : Optional [ Set [Ursula ] ] = None ,
385
385
* args , ** kwargs ,
386
386
) -> None :
387
387
@@ -408,11 +408,12 @@ def make_arrangement(self, ursula: Ursula, *args, **kwargs):
408
408
raise NotImplementedError
409
409
410
410
@abstractmethod
411
- def sample_essential (self , quantity : int , handpicked_ursulas : Set [Ursula ] = None ) -> Set [Ursula ]:
411
+ def sample_essential (self , quantity : int , handpicked_ursulas : Set [Ursula ]) -> Set [Ursula ]:
412
412
raise NotImplementedError
413
413
414
- def sample (self , handpicked_ursulas : Set [Ursula ] = None ) -> Set [Ursula ]:
415
- selected_ursulas = set (handpicked_ursulas ) if handpicked_ursulas else set ()
414
+ def sample (self , handpicked_ursulas : Optional [Set [Ursula ]] = None ) -> Set [Ursula ]:
415
+ handpicked_ursulas = handpicked_ursulas if handpicked_ursulas else set ()
416
+ selected_ursulas = set (handpicked_ursulas )
416
417
417
418
# Calculate the target sample quantity
418
419
target_sample_quantity = self .n - len (selected_ursulas )
@@ -475,11 +476,11 @@ def make_arrangements(self, *args, **kwargs) -> None:
475
476
"Pass them here as handpicked_ursulas." .format (self .n )
476
477
raise self .MoreKFragsThanArrangements (error ) # TODO: NotEnoughUrsulas where in the exception tree is this?
477
478
478
- def sample_essential (self , quantity : int , handpicked_ursulas : Set [Ursula ] = None ) -> Set [Ursula ]:
479
+ def sample_essential (self , quantity : int , handpicked_ursulas : Set [Ursula ]) -> Set [Ursula ]:
479
480
known_nodes = self .alice .known_nodes
480
481
if handpicked_ursulas :
481
482
# Prevent re-sampling of handpicked ursulas.
482
- known_nodes = set (known_nodes ) - set ( handpicked_ursulas )
483
+ known_nodes = set (known_nodes ) - handpicked_ursulas
483
484
sampled_ursulas = set (random .sample (k = quantity , population = list (known_nodes )))
484
485
return sampled_ursulas
485
486
@@ -572,57 +573,68 @@ def generate_policy_parameters(n: int,
572
573
params = dict (rate = rate , value = value )
573
574
return params
574
575
575
- def __find_ursulas (self ,
576
- ether_addresses : List [str ],
577
- target_quantity : int ,
578
- timeout : int = 10 ) -> set : # TODO #843: Make timeout configurable
576
+ def sample_essential (self ,
577
+ quantity : int ,
578
+ handpicked_ursulas : Set [Ursula ],
579
+ learner_timeout : int = 1 ,
580
+ timeout : int = 10 ) -> Set [Ursula ]:
579
581
580
- start_time = maya .now () # marker for timeout calculation
582
+ selected_addresses = set (handpicked_ursulas )
583
+ quantity_remaining = quantity
581
584
582
- found_ursulas , unknown_addresses = set (), deque ()
583
- while len (found_ursulas ) < target_quantity : # until there are enough Ursulas
585
+ # Need to sample some stakers
584
586
585
- delta = maya .now () - start_time # check for a timeout
586
- if delta .total_seconds () >= timeout :
587
- missing_nodes = ', ' .join (a for a in unknown_addresses )
588
- raise RuntimeError ("Timed out after {} seconds; Cannot find {}." .format (timeout , missing_nodes ))
587
+ reservoir = self .alice .get_stakers_reservoir (duration = self .duration_periods ,
588
+ without = handpicked_ursulas )
589
+ if len (reservoir ) < quantity_remaining :
590
+ error = f"Cannot create policy with { quantity } arrangements"
591
+ raise self .NotEnoughBlockchainUrsulas (error )
589
592
590
- # Select an ether_address: Prefer the selection pool, then unknowns queue
591
- if ether_addresses :
592
- ether_address = ether_addresses .pop ()
593
- else :
594
- ether_address = unknown_addresses .popleft ()
593
+ to_check = reservoir .draw (quantity_remaining )
595
594
596
- try :
597
- # Check if this is a known node.
598
- selected_ursula = self .alice .known_nodes [ether_address ]
595
+ # Sample stakers in a loop and feed them to the learner to check
596
+ # until we have enough in `selected_addresses`.
599
597
600
- except KeyError :
601
- # Unknown Node
602
- self .alice .learn_about_specific_nodes ({ether_address }) # enter address in learning loop
603
- unknown_addresses .append (ether_address )
604
- continue
598
+ start_time = maya .now ()
599
+ new_to_check = to_check
600
+
601
+ while True :
602
+
603
+ # Check if the sampled addresses are already known.
604
+ # If we're lucky, we won't have to wait for the learner iteration to finish.
605
+ known = list (filter (lambda x : x in self .alice .known_nodes , to_check ))
606
+ to_check = list (filter (lambda x : x not in self .alice .known_nodes , to_check ))
605
607
608
+ known = known [:min (len (known ), quantity_remaining )] # we only need so many
609
+ selected_addresses .update (known )
610
+ quantity_remaining -= len (known )
611
+
612
+ if quantity_remaining == 0 :
613
+ break
606
614
else :
607
- # Known Node
608
- found_ursulas . add ( selected_ursula ) # We already knew, or just learned about this ursula
615
+ new_to_check = reservoir . draw_at_most ( quantity_remaining )
616
+ to_check . extend ( new_to_check )
609
617
610
- return found_ursulas
618
+ # Feed newly sampled stakers to the learner
619
+ self .alice .learn_about_specific_nodes (new_to_check )
611
620
612
- def sample_essential (self , quantity : int , handpicked_ursulas : Set [Ursula ] = None ) -> Set [Ursula ]:
613
- # TODO: Prevent re-sampling of handpicked ursulas.
614
- selected_addresses = set ()
615
- try :
616
- sampled_addresses = self .alice .recruit (quantity = quantity ,
617
- duration = self .duration_periods )
618
- except StakingEscrowAgent .NotEnoughStakers as e :
619
- error = f"Cannot create policy with { quantity } arrangements: { e } "
620
- raise self .NotEnoughBlockchainUrsulas (error )
621
+ # TODO: would be nice to wait for the learner to finish an iteration here,
622
+ # because if it hasn't, we really have nothing to do.
623
+ time .sleep (learner_timeout )
624
+
625
+ delta = maya .now () - start_time
626
+ if delta .total_seconds () >= timeout :
627
+ still_checking = ', ' .join (to_check )
628
+ raise RuntimeError (f"Timed out after { timeout } seconds; "
629
+ f"need { quantity } more, still checking { still_checking } ." )
630
+
631
+ found_ursulas = list (selected_addresses )
632
+
633
+ # Randomize the output to avoid the largest stakers always being the first in the list
634
+ system_random = random .SystemRandom ()
635
+ system_random .shuffle (found_ursulas ) # inplace
621
636
622
- # Capture the selection and search the network for those Ursulas
623
- selected_addresses .update (sampled_addresses )
624
- found_ursulas = self .__find_ursulas (sampled_addresses , quantity )
625
- return found_ursulas
637
+ return set (found_ursulas )
626
638
627
639
def publish_to_blockchain (self ) -> dict :
628
640
0 commit comments