def reserve_funds_or_cancel(self, client_blob_request): reserved_points = self._reserve_points(client_blob_request.max_pay_units) if reserved_points is not None: return reserved_points client_blob_request.cancel(InsufficientFundsError()) client_blob_request.finished_deferred.addErrback(lambda _: True) raise InsufficientFundsError()
def send_next_request(self, peer, protocol): sent_request = False if self._blobs_to_download() and self._should_send_request_to(peer): a_r = self._get_availability_request(peer) d_r = self._get_download_request(peer) p_r = None if a_r is not None or d_r is not None: p_r = self._get_price_request(peer, protocol) if a_r is not None: d1 = protocol.add_request(a_r) d1.addCallback(self._handle_availability, peer, a_r) d1.addErrback(self._request_failed, "availability request", peer) sent_request = True if d_r is not None: reserved_points = self._reserve_points(peer, protocol, d_r.max_pay_units) if reserved_points is not None: # Note: The following three callbacks will be called when the blob has been # fully downloaded or canceled d_r.finished_deferred.addCallbacks( self._download_succeeded, self._download_failed, callbackArgs=(peer, d_r.blob), errbackArgs=(peer, )) d_r.finished_deferred.addBoth(self._pay_or_cancel_payment, protocol, reserved_points, d_r.blob) d_r.finished_deferred.addErrback( self._handle_download_error, peer, d_r.blob) d2 = protocol.add_blob_request(d_r) # Note: The following two callbacks will be called as soon as the peer sends its # response, which will be before the blob has finished downloading, but may be # after the blob has been canceled. For example, # 1) client sends request to Peer A # 2) the blob is finished downloading from peer B, and therefore this one is canceled # 3) client receives response from Peer A # Therefore, these callbacks shouldn't rely on there being a blob about to be # downloaded. d2.addCallback(self._handle_incoming_blob, peer, d_r) d2.addErrback(self._request_failed, "download request", peer) sent_request = True else: d_r.cancel(InsufficientFundsError()) d_r.finished_deferred.addErrback(lambda _: True) return defer.fail(InsufficientFundsError()) if sent_request is True: if p_r is not None: d3 = protocol.add_request(p_r) d3.addCallback(self._handle_price_response, peer, p_r, protocol) d3.addErrback(self._request_failed, "price request", peer) return defer.succeed(sent_request)
def send_next_request(self, peer, protocol): # Basic idea: # If the peer has been sending us blob hashes to download recently (10 minutes?), # send back an example of one (the most recent?) so that it can # keep sending us more like it. Otherwise, just ask for # valuable blobs sent_request = False if self._should_send_request_to(peer): v_r = self._get_valuable_blob_request(peer) if v_r is not None: v_p_r = self._get_valuable_price_request(peer, protocol) reserved_points = self._reserve_points_valuable( peer, protocol, v_r.max_pay_units) if reserved_points is not None: d1 = protocol.add_request(v_r) d1.addCallback(self._handle_valuable_blob_response, peer, v_r) d1.addBoth(self._pay_or_cancel_payment, protocol, reserved_points, self._info_protocol_prices) d1.addErrback(self._request_failed, "valuable blob request", peer) sent_request = True if v_p_r is not None: d2 = protocol.add_request(v_p_r) d2.addCallback(self._handle_valuable_price_response, peer, v_p_r, protocol) d2.addErrback(self._request_failed, "valuable price request", peer) else: return defer.fail(InsufficientFundsError()) i_r = self._get_info_request(peer) if i_r is not None: i_p_r = self._get_info_price_request(peer, protocol) reserved_points = self._reserve_points_info( peer, protocol, i_r.max_pay_units) if reserved_points is not None: d3 = protocol.add_request(i_r) d3.addCallback(self._handle_info_response, peer, i_r, protocol, reserved_points) d3.addBoth(self._pay_or_cancel_payment, protocol, reserved_points, self._valuable_protocol_prices) d3.addErrback(self._request_failed, "info request", peer, reserved_points) sent_request = True if i_p_r is not None: d4 = protocol.add_request(i_p_r) d4.addCallback(self._handle_info_price_response, peer, i_p_r, protocol) d4.addErrback(self._request_failed, "info price request", peer) else: return defer.fail(InsufficientFundsError()) return defer.succeed(sent_request)
def claim_name(self, name, bid, metadata): """ Claim a name, or update if name already claimed by user @param name: str, name to claim @param bid: float, bid amount @param metadata: Metadata compliant dict @return: Deferred which returns a dict containing below items txid - txid of the resulting transaction nout - nout of the resulting claim fee - transaction fee paid to make claim claim_id - claim id of the claim """ _metadata = Metadata(metadata) my_claim = yield self.get_my_claim(name) if my_claim: log.info("Updating claim") if self.get_balance() < Decimal(bid) - Decimal(my_claim['amount']): raise InsufficientFundsError() new_metadata = yield self.update_metadata(_metadata, my_claim['value']) old_claim_outpoint = ClaimOutpoint(my_claim['txid'], my_claim['nout']) claim = yield self._send_name_claim_update(name, my_claim['claim_id'], old_claim_outpoint, new_metadata, bid) claim['claim_id'] = my_claim['claim_id'] else: log.info("Making a new claim") if self.get_balance() < bid: raise InsufficientFundsError() claim = yield self._send_name_claim(name, _metadata, bid) if not claim['success']: msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) raise Exception(msg) claim = self._process_claim_out(claim) claim_outpoint = ClaimOutpoint(claim['txid'], claim['nout']) log.info("Saving metadata for claim %s %d", claim['txid'], claim['nout']) yield self._save_name_metadata(name, claim_outpoint, _metadata['sources']['lbry_sd_hash']) defer.returnValue(claim)
def start(self, stream_info, name): def _cause_timeout(err): log.info('Cancelling download') self.timeout_counter = self.timeout * 2 def _set_status(x, status): log.info("Download lbry://%s status changed to %s" % (self.resolved_name, status)) self.code = next(s for s in STREAM_STAGES if s[0] == status) return x def get_downloader_factory(metadata): for factory in metadata.factories: if isinstance(factory, ManagedEncryptedFileDownloaderFactory): return factory, metadata raise Exception('No suitable factory was found in {}'.format( metadata.factories)) def make_downloader(args): factory, metadata = args return factory.make_downloader( metadata, [self.data_rate, True], self.payment_rate_manager, download_directory=self.download_directory, file_name=self.file_name) self.resolved_name = name self.stream_info = deepcopy(stream_info) self.description = self.stream_info['description'] self.stream_hash = self.stream_info['sources']['lbry_sd_hash'] if 'fee' in self.stream_info: self.fee = FeeValidator(self.stream_info['fee']) max_key_fee = self._convert_max_fee() converted_fee = self.exchange_rate_manager.to_lbc(self.fee).amount if converted_fee > self.wallet.get_balance(): log.warning("Insufficient funds to download lbry://%s", self.resolved_name) return defer.fail(InsufficientFundsError()) if converted_fee > max_key_fee: log.warning( "Key fee %f above limit of %f didn't download lbry://%s", converted_fee, max_key_fee, self.resolved_name) return defer.fail(KeyFeeAboveMaxAllowed()) log.info("Key fee %f below limit of %f, downloading lbry://%s", converted_fee, max_key_fee, self.resolved_name) self.checker.start(1) self.d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) self.d.addCallback(lambda _: download_sd_blob( self.session, self.stream_hash, self.payment_rate_manager)) self.d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self.d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) self.d.addCallback(get_downloader_factory) self.d.addCallback(make_downloader) self.d.addCallbacks(self._start_download, _cause_timeout) self.d.callback(None) return self.finished
def _pay_key_fee(self, address, fee_lbc, name): log.info("Pay key fee %f --> %s", fee_lbc, address) reserved_points = self.wallet.reserve_points(address, fee_lbc) if reserved_points is None: raise InsufficientFundsError( 'Unable to pay the key fee of %s for %s' % (fee_lbc, name)) return self.wallet.send_points_to_address(reserved_points, fee_lbc)
def claim_name(self, name, bid, metadata, certificate_id=None, claim_address=None, change_address=None): """ Claim a name, or update if name already claimed by user @param name: str, name to claim @param bid: float, bid amount @param metadata: ClaimDict compliant dict @param certificate_id: str (optional), claim id of channel certificate @param claim_address: str (optional), address to send claim to @param change_address: str (optional), address to send change @return: Deferred which returns a dict containing below items txid - txid of the resulting transaction nout - nout of the resulting claim fee - transaction fee paid to make claim claim_id - claim id of the claim """ decoded = ClaimDict.load_dict(metadata) serialized = decoded.serialized if self.get_balance() < Decimal(bid): raise InsufficientFundsError() claim = yield self._send_name_claim(name, serialized.encode('hex'), bid, certificate_id, claim_address, change_address) if not claim['success']: log.error(claim) msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) raise Exception(msg) claim = self._process_claim_out(claim) yield self.storage.save_claim(self._get_temp_claim_info(claim, name, bid), smart_decode(claim['value'])) defer.returnValue(claim)
def _pay_key_fee(self): if self.fee is not None: fee_lbc = self.exchange_rate_manager.to_lbc(self.fee).amount reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) if reserved_points is None: return defer.fail(InsufficientFundsError()) return self.wallet.send_points_to_address(reserved_points, fee_lbc) return defer.succeed(None)
def _pay_key_fee(self, address, fee_lbc, name): log.info("Pay key fee %s --> %s", dewies_to_lbc(fee_lbc), address) reserved_points = self.wallet.reserve_points(address, fee_lbc) if reserved_points is None: raise InsufficientFundsError( 'Unable to pay the key fee of {} for {}'.format( dewies_to_lbc(fee_lbc), name)) return f2d(self.wallet.send_points_to_address(reserved_points, fee_lbc))
def _claim_or_update(claim, metadata, _bid): if not claim: log.debug("No own claim yet, making a new one") if self.get_balance() < _bid: raise InsufficientFundsError() return self._send_name_claim(name, metadata, _bid) else: log.debug("Updating over own claim") if self.get_balance() < _bid - claim['amount']: raise InsufficientFundsError() d = self.update_metadata(metadata, claim['value']) claim_outpoint = ClaimOutpoint(claim['txid'], claim['nOut']) d.addCallback( lambda new_metadata: self._send_name_claim_update( name, claim['claim_id'], claim_outpoint, new_metadata, _bid)) d.addCallback(lambda claim_out: claim_out.update( {'claim_id': claim['claim_id']})) return d
def check_fee(self, fee): validated_fee = FeeValidator(fee) max_key_fee = self.convert_max_fee() converted_fee = self.exchange_rate_manager.to_lbc(validated_fee).amount if converted_fee > self.wallet.get_balance(): raise InsufficientFundsError('Unable to pay the key fee of %s' % converted_fee) if converted_fee > max_key_fee: raise KeyFeeAboveMaxAllowed('Key fee %s above max allowed %s' % (converted_fee, max_key_fee)) return validated_fee
def _pay_key_fee(self): if self.fee is not None: fee_lbc = self.exchange_rate_manager.to_lbc(self.fee).amount reserved_points = self.wallet.reserve_points(self.fee.address, fee_lbc) if reserved_points is None: log.warning('Unable to pay the key fee of %s for %s', fee_lbc, self.resolved_name) # TODO: If we get here, nobody will know that there was an error # as nobody actually cares about self._d return defer.fail(InsufficientFundsError()) return self.wallet.send_points_to_address(reserved_points, fee_lbc) return defer.succeed(None)
def support_claim(self, name, claim_id, amount): def _parse_support_claim_out(claim_out): if not claim_out['success']: msg = 'Support of {}:{} failed: {}'.format(name, claim_id, claim_out['reason']) raise Exception(msg) claim_out = self._process_claim_out(claim_out) return defer.succeed(claim_out) if self.get_balance() < amount: raise InsufficientFundsError() d = self._support_claim(name, claim_id, amount) d.addCallback(lambda claim_out: _parse_support_claim_out(claim_out)) return d
def check_fee_and_convert(self, fee): max_key_fee_amount = self.convert_max_fee() converted_fee_amount = self.exchange_rate_manager.convert_currency(fee.currency, "LBC", fee.amount) if converted_fee_amount > self.wallet.get_balance(): raise InsufficientFundsError('Unable to pay the key fee of %s' % converted_fee_amount) if converted_fee_amount > max_key_fee_amount: raise KeyFeeAboveMaxAllowed('Key fee %s above max allowed %s' % (converted_fee_amount, max_key_fee_amount)) converted_fee = { 'currency': 'LBC', 'amount': converted_fee_amount, 'address': fee.address } return Fee(converted_fee)
def check_fee_and_convert(self, fee): max_key_fee_amount = self.convert_max_fee() converted_fee_amount = self.exchange_rate_manager.convert_currency( fee.currency, "LBC", fee.amount) if converted_fee_amount > (yield f2d( self.wallet.default_account.get_balance())): raise InsufficientFundsError('Unable to pay the key fee of %s' % converted_fee_amount) if converted_fee_amount > max_key_fee_amount and not self.disable_max_key_fee: raise KeyFeeAboveMaxAllowed( 'Key fee {} above max allowed {}'.format( converted_fee_amount, max_key_fee_amount)) converted_fee = { 'currency': 'LBC', 'amount': converted_fee_amount, 'address': fee.address } return Fee(converted_fee)
def claim_name(self, name, bid, metadata, certificate_id=None): """ Claim a name, or update if name already claimed by user @param name: str, name to claim @param bid: float, bid amount @param metadata: ClaimDict compliant dict @param certificate_id: str (optional), claim id of channel certificate @return: Deferred which returns a dict containing below items txid - txid of the resulting transaction nout - nout of the resulting claim fee - transaction fee paid to make claim claim_id - claim id of the claim """ decoded = ClaimDict.load_dict(metadata) serialized = decoded.serialized if self.get_balance() < Decimal(bid): raise InsufficientFundsError() claim = yield self._send_name_claim(name, serialized.encode('hex'), bid, certificate_id) if not claim['success']: msg = 'Claim to name {} failed: {}'.format(name, claim['reason']) raise Exception(msg) claim = self._process_claim_out(claim) claim_outpoint = ClaimOutpoint(claim['txid'], claim['nout']) log.info("Saving metadata for claim %s %d", claim['txid'], claim['nout']) yield self._update_claimid(claim['claim_id'], name, claim_outpoint) yield self._save_name_metadata(name, claim_outpoint, decoded.source_hash) defer.returnValue(claim)
def send_next_request(self, peer, protocol): if self._finished_discovery( ) is False and self._should_send_request_to(peer) is True: p_r = None if not self._price_settled(protocol): p_r = self._get_price_request(peer, protocol) d_r = self._get_discover_request(peer) reserved_points = self._reserve_points(peer, protocol, d_r.max_pay_units) if reserved_points is not None: d1 = protocol.add_request(d_r) d1.addCallback(self._handle_discover_response, peer, d_r) d1.addBoth(self._pay_or_cancel_payment, protocol, reserved_points) d1.addErrback(self._request_failed, peer) if p_r is not None: d2 = protocol.add_request(p_r) d2.addCallback(self._handle_price_response, peer, p_r, protocol) d2.addErrback(self._request_failed, peer) return defer.succeed(True) else: return defer.fail(InsufficientFundsError()) return defer.succeed(False)
def start(self, stream_info, name): def _cancel(err): # this callback sequence gets cancelled in check_status if # it takes too long when that happens, we want the logic # to live in check_status if err.check(defer.CancelledError): return if self.checker: self.checker.stop() self.finished.errback(err) def _set_status(x, status): log.info("Download lbry://%s status changed to %s" % (self.resolved_name, status)) self.code = next(s for s in STREAM_STAGES if s[0] == status) return x def get_downloader_factory(metadata): for factory in metadata.factories: if isinstance(factory, ManagedEncryptedFileDownloaderFactory): return factory, metadata raise Exception('No suitable factory was found in {}'.format(metadata.factories)) def make_downloader(args): factory, metadata = args return factory.make_downloader(metadata, [self.data_rate, True], self.payment_rate_manager, download_directory=self.download_directory, file_name=self.file_name) self.resolved_name = name self.stream_info = deepcopy(stream_info) self.description = self.stream_info['description'] self.sd_hash = self.stream_info['sources']['lbry_sd_hash'] if 'fee' in self.stream_info: self.fee = FeeValidator(self.stream_info['fee']) max_key_fee = self._convert_max_fee() converted_fee = self.exchange_rate_manager.to_lbc(self.fee).amount if converted_fee > self.wallet.get_balance(): msg = "Insufficient funds to download lbry://{}. Need {:0.2f}, have {:0.2f}".format( self.resolved_name, converted_fee, self.wallet.get_balance()) raise InsufficientFundsError(msg) if converted_fee > max_key_fee: msg = "Key fee {:0.2f} above limit of {:0.2f} didn't download lbry://{}".format( converted_fee, max_key_fee, self.resolved_name) raise KeyFeeAboveMaxAllowed(msg) log.info( "Key fee %f below limit of %f, downloading lbry://%s", converted_fee, max_key_fee, self.resolved_name) self.checker.start(1) self._d.addCallback(lambda _: _set_status(None, DOWNLOAD_METADATA_CODE)) self._d.addCallback(lambda _: download_sd_blob( self.session, self.sd_hash, self.payment_rate_manager)) self._d.addCallback(self.sd_identifier.get_metadata_for_sd_blob) self._d.addCallback(lambda r: _set_status(r, DOWNLOAD_RUNNING_CODE)) self._d.addCallback(get_downloader_factory) self._d.addCallback(make_downloader) self._d.addCallbacks(self._start_download, _cancel) self._d.callback(None) return self.finished