def _enact_arrangements( self, network_middleware: RestMiddleware, arrangements: Dict[Ursula, Arrangement], publication_transaction: Optional[HexBytes] = None, publish_treasure_map: bool = True, timeout: int = 10, ): """ Attempts to distribute kfrags to Ursulas that accepted arrangements earlier. """ def worker(ursula_and_kfrag): ursula, kfrag = ursula_and_kfrag arrangement = arrangements[ursula] # TODO: seems like it would be enough to just encrypt this with Ursula's public key, # and not create a whole capsule. # Can't change for now since it's node protocol. payload = self._make_enactment_payload(publication_transaction, kfrag) message_kit, _signature = self.alice.encrypt_for(ursula, payload) try: # TODO: Concurrency response = network_middleware.enact_policy( ursula, arrangement.id, message_kit.to_bytes()) except network_middleware.UnexpectedResponse as e: status = e.status else: status = response.status_code return status value_factory = AllAtOnceFactory(list(zip(arrangements, self.kfrags))) worker_pool = WorkerPool(worker=worker, value_factory=value_factory, target_successes=self.n, timeout=timeout, threadpool_size=self.n) worker_pool.start() # Block until everything is complete. We need all the workers to finish. worker_pool.join() successes = worker_pool.get_successes() if len(successes) != self.n: raise Policy.EnactmentError() # TODO: Enable re-tries? statuses = { ursula_and_kfrag[0].checksum_address: status for ursula_and_kfrag, status in successes.items() } if not all(status == 200 for status in statuses.values()): report = "\n".join(f"{address}: {status}" for address, status in statuses.items()) self.log.debug( f"Policy enactment failed. Request statuses:\n{report}") # OK, let's check: if two or more Ursulas claimed we didn't pay, # we need to re-evaulate our situation here. number_of_claims_of_freeloading = sum( status == 402 for status in statuses.values()) # TODO: a better exception here? if number_of_claims_of_freeloading > 2: raise self.alice.NotEnoughNodes # otherwise just raise a more generic error raise Policy.EnactmentError()
def _make_arrangements( self, network_middleware: RestMiddleware, handpicked_ursulas: Optional[Iterable[Ursula]] = None, timeout: int = 10, ) -> Dict[Ursula, Arrangement]: """ Pick some Ursula addresses and send them arrangement proposals. Returns a dictionary of Ursulas to Arrangements if it managed to get `n` responses. """ if handpicked_ursulas is None: handpicked_ursulas = [] handpicked_addresses = [ ursula.checksum_address for ursula in handpicked_ursulas ] reservoir = self._make_reservoir(handpicked_addresses) value_factory = PrefetchStrategy(reservoir, self.n) def worker(address): return self._propose_arrangement(address, network_middleware) self.alice.block_until_number_of_known_nodes_is( self.n, learn_on_this_thread=True, eager=True) worker_pool = WorkerPool(worker=worker, value_factory=value_factory, target_successes=self.n, timeout=timeout, stagger_timeout=1, threadpool_size=self.n) worker_pool.start() try: successes = worker_pool.block_until_target_successes() except (WorkerPool.OutOfValues, WorkerPool.TimedOut): # It's possible to raise some other exceptions here, # but we will use the logic below. successes = worker_pool.get_successes() finally: worker_pool.cancel() worker_pool.join() accepted_arrangements = { ursula: arrangement for ursula, arrangement in successes.values() } failures = worker_pool.get_failures() accepted_addresses = ", ".join(ursula.checksum_address for ursula in accepted_arrangements) if len(accepted_arrangements) < self.n: rejected_proposals = "\n".join( f"{address}: {value}" for address, (type_, value, traceback) in failures.items()) self.log.debug( "Could not find enough Ursulas to accept proposals.\n" f"Accepted: {accepted_addresses}\n" f"Rejected:\n{rejected_proposals}") raise self._not_enough_ursulas_exception() else: self.log.debug( f"Finished proposing arrangements; accepted: {accepted_addresses}" ) return accepted_arrangements