def test_validate_ecdsa_signature(self): cert = ClaimDict.generate_certificate(nist384p_private_key, curve=NIST384p) signed = ClaimDict.load_dict(example_010).sign(nist384p_private_key, claim_address_2, claim_id_1, curve=NIST384p) self.assertDictEqual(signed.claim_dict, claim_010_signed_nist384p) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertEqual(signed_copy.validate_signature(claim_address_2, cert), True)
def test_alpha2(self): prefixes = [ 'en', 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 'el', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu' ] for prefix in prefixes: metadata = deepcopy(example_010) metadata['stream']['metadata']['language'] = prefix claim = ClaimDict.load_dict(metadata) serialized = claim.serialized self.assertDictEqual( metadata, dict(ClaimDict.deserialize(serialized).claim_dict))
def test_validate_what_cant_be_serialized_back_even_by_loading_back_from_dictionary( self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) original = ClaimDict.load_dict(example_010).serialized altered = original + b'\x00\x01\x02\x30\x50\x80\x99' # pretend this extra trash is from some unknown protobuf # manually sign signer = get_signer(SECP256k1).load_pem(secp256k1_private_key) signature = signer.sign( b'example', decode_address(claim_address_2), altered, binascii.unhexlify(claim_id_1), ) detached_sig = Signature( NAMED_SECP256K1(signature, binascii.unhexlify(claim_id_1), altered)) signed = detached_sig.serialized self.assertEqual(signed[85:], altered) signed_copy = ClaimDict.deserialize(signed) signed_copy = ClaimDict.load_dict(signed_copy.claim_dict) self.assertEqual( signed_copy.validate_signature(claim_address_2, cert, name='example'), True) self.assertEqual(signed, signed_copy.serialized)
def smart_decode(claim_value): """ Decode a claim value Try decoding claim protobuf, if this fails try decoding json and migrating it. If unable to decode or migrate, raise DecodeError """ # if already decoded, return if isinstance(claim_value, ClaimDict): return claim_value elif isinstance(claim_value, dict): return ClaimDict.load_dict(claim_value) try: claim_value = binascii.unhexlify(claim_value) except (TypeError, ValueError): pass if claim_value[0] in ['{', ord('{')]: try: if six.PY3 and isinstance(claim_value, six.binary_type): claim_value = claim_value.decode() decoded_json = json.loads(claim_value) return migrate_json_claim_value(decoded_json) except (ValueError, TypeError): pass try: if six.PY3 and isinstance(claim_value, six.text_type): claim_value = claim_value.encode() return ClaimDict.deserialize(claim_value) except (DecodeError, InvalidAddress, KeyError, TypeError): raise DecodeError(claim_value)
def test_encode_decode(self): test_claim = ClaimDict.load_dict(example_010) self.assertEqual(test_claim.is_certificate, False) self.assertDictEqual(test_claim.claim_dict, example_010) test_pb = test_claim.protobuf self.assertDictEqual(ClaimDict.load_protobuf(test_pb).claim_dict, example_010) self.assertEqual(test_pb.ByteSize(), ClaimDict.load_protobuf(test_pb).protobuf_len) self.assertEqual(test_claim.json_len, ClaimDict.load_protobuf(test_pb).json_len)
def test_fail_to_validate_with_no_claim_address(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1) self.assertDictEqual(signed.claim_dict, claim_010_signed_secp256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertRaises(Exception, signed_copy.validate_signature, None, cert)
def test_fail_to_validate_fake_ecdsa_signature(self): signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) fake_key = get_signer(SECP256k1).generate().private_key.to_pem() fake_cert = ClaimDict.generate_certificate(fake_key, curve=SECP256k1) self.assertRaises(ecdsa.keys.BadSignatureError, signed_copy.validate_signature, claim_address_2, fake_cert)
def test_validate_ecdsa_signature(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1) self.assertDictEqual(signed.claim_dict, claim_010_signed_secp256k1) signed_copy = ClaimDict.load_protobuf(signed.protobuf) self.assertEqual(signed_copy.validate_signature(claim_address_2, cert), True)
def test_fail_to_validate_ecdsa_sig_for_altered_claim(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) altered = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) sd_hash = altered['stream']['source']['source'] altered['stream']['source']['source'] = sd_hash[::-1] altered_copy = ClaimDict.load_dict(altered.claim_dict) self.assertRaises(ecdsa.keys.BadSignatureError, altered_copy.validate_signature, claim_address_1, cert)
def test_fail_to_sign_with_no_claim_address(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) self.assertRaises(Exception, ClaimDict.load_dict(example_010).sign, secp256k1_private_key, None, claim_id_1, curve=SECP256k1)
def test_fail_to_validate_fake_ecdsa_signature(self): signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1, name='example', force_detached=True) signed_copy = ClaimDict.deserialize(signed.serialized) fake_key = get_signer(SECP256k1).generate().private_key.to_pem() fake_cert = ClaimDict.generate_certificate(fake_key, curve=SECP256k1) self.assertRaises(ecdsa.keys.BadSignatureError, signed_copy.validate_signature, claim_address_2, fake_cert, 'example')
def test_validate_detached_named_ecdsa_signature(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1, name='example', force_detached=True) signed_copy = ClaimDict.deserialize(signed.serialized) self.assertEqual( signed_copy.validate_signature(claim_address_2, cert, name='example'), True)
async def download_stream_from_claim(self, node: 'Node', claim_info: typing.Dict, file_name: typing.Optional[str] = None, timeout: typing.Optional[float] = 60, fee_amount: typing.Optional[float] = 0.0, fee_address: typing.Optional[str] = None, should_pay: typing.Optional[bool] = True) -> typing.Optional[ManagedStream]: claim = ClaimDict.load_dict(claim_info['value']) sd_hash = claim.source_hash.decode() if sd_hash in self.starting_streams: return await self.starting_streams[sd_hash] already_started = tuple(filter(lambda s: s.descriptor.sd_hash == sd_hash, self.streams)) if already_started: return already_started[0] if should_pay and fee_address and fee_amount and fee_amount > await self.wallet.default_account.get_balance(): raise Exception("not enough funds") self.starting_streams[sd_hash] = asyncio.Future(loop=self.loop) stream_task = self.loop.create_task( self._download_stream_from_claim(node, self.config.download_dir, claim_info, file_name) ) try: await asyncio.wait_for(stream_task, timeout or self.config.download_timeout) stream = await stream_task self.starting_streams[sd_hash].set_result(stream) if should_pay and fee_address and fee_amount: await self.wallet.send_amount_to_address(lbc_to_dewies(str(fee_amount)), fee_address.encode('latin1')) return stream except (asyncio.TimeoutError, asyncio.CancelledError): return finally: if sd_hash in self.starting_streams: del self.starting_streams[sd_hash]
def test_successful_send_name_claim(self): expected_claim_out = { "claim_id": "f43dc06256a69988bdbea09a58c80493ba15dcfa", "fee": "0.00012", "nout": 0, "success": True, "txid": "6f8180002ef4d21f5b09ca7d9648a54d213c666daf8639dc283e2fd47450269e", "value": ClaimDict.load_dict(test_claim_dict).serialized.encode('hex'), "claim_address": "", "channel_claim_id": "", "channel_name": "" } def success_send_name_claim(self, name, val, amount, certificate_id=None, claim_address=None, change_address=None): return defer.succeed(expected_claim_out) self.wallet._send_name_claim = success_send_name_claim claim_out = yield self.wallet.claim_name('test', 1, test_claim_dict) self.assertNotIn('success', claim_out) self.assertEqual(expected_claim_out['claim_id'], claim_out['claim_id']) self.assertEqual(expected_claim_out['fee'], claim_out['fee']) self.assertEqual(expected_claim_out['nout'], claim_out['nout']) self.assertEqual(expected_claim_out['txid'], claim_out['txid']) self.assertEqual(expected_claim_out['value'], claim_out['value'])
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None): claim = ClaimDict.load_dict(claim_dict) if not claim_address: claim_address = await account.receiving.get_or_create_usable_address( ) if certificate: claim = claim.sign(certificate.private_key, claim_address, certificate.claim_id, curve=SECP256k1) existing_claims = await account.get_claims(claim_name=name) if len(existing_claims) == 0: tx = await Transaction.claim(name, claim, amount, claim_address, [account], account) elif len(existing_claims) == 1: tx = await Transaction.update(existing_claims[0], claim, amount, claim_address, [account], account) else: raise NameError( f"More than one other claim exists with the name '{name}'.") await account.ledger.broadcast(tx) await self.old_db.save_claims([ self._old_get_temp_claim_info(tx, tx.outputs[0], claim_address, claim_dict, name, amount) ]).asFuture(asyncio.get_event_loop()) # TODO: release reserved tx outputs in case anything fails by this point return tx
async def test_new_signature_model(self): address1, address2 = await self.account.receiving.get_addresses( limit=2, only_usable=True) sendtxid1 = await self.blockchain.send_to_address(address1, 5) sendtxid2 = await self.blockchain.send_to_address(address2, 5) await self.blockchain.generate(1) await asyncio.wait([ self.on_transaction_id(sendtxid1), self.on_transaction_id(sendtxid2) ]) self.assertEqual(d2l(await self.account.get_balance()), '10.0') cert, key = generate_certificate() cert_tx = await Transaction.claim('@bar', cert, l2d('1.0'), address1, [self.account], self.account) claim = ClaimDict.load_dict(example_claim_dict) claim = claim.sign(key, address1, cert_tx.outputs[0].claim_id, name='foo', curve=SECP256k1, force_detached=True) claim_tx = await Transaction.claim('foo', claim, l2d('1.0'), address1, [self.account], self.account) await self.broadcast(cert_tx) await self.broadcast(claim_tx) await self.ledger.wait(claim_tx) await self.blockchain.generate(1) await self.ledger.wait(claim_tx) response = await self.ledger.resolve(0, 10, 'lbry://@bar/foo') self.assertIn('lbry://@bar/foo', response) self.assertIn('claim', response['lbry://@bar/foo'])
def test_remove_signature_equals_unsigned(self): unsigned = ClaimDict.load_dict(example_010) signed = unsigned.sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1) self.assertEqual(unsigned.serialized, signed.serialized_no_signature)
def test_validate_detached_named_ecdsa_signature_from_dict(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) self.assertDictEqual(cert.claim_dict, secp256k1_cert) signed = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_2, claim_id_1, curve=SECP256k1, name='example', force_detached=True) self.assertEqual( signed.claim_dict['publisherSignature']['detached_signature'], binascii.hexlify(signed.serialized).decode()) signed_copy = ClaimDict.load_dict(signed.claim_dict) self.assertEqual( signed_copy.validate_signature(claim_address_2, cert, name='example'), True)
async def download_stream_from_uri(self, uri, exchange_rate_manager: 'ExchangeRateManager', file_name: typing.Optional[str] = None, timeout: typing.Optional[float] = None) -> typing.Optional[ManagedStream]: timeout = timeout or self.config.download_timeout parsed_uri = parse_lbry_uri(uri) if parsed_uri.is_channel: raise Exception("cannot download a channel claim, specify a /path") resolved = (await self.wallet.resolve(uri)).get(uri, {}) resolved = resolved if 'value' in resolved else resolved.get('claim') if not resolved: raise ResolveError( "Failed to resolve stream at lbry://{}".format(uri.replace("lbry://", "")) ) if 'error' in resolved: raise ResolveError(f"error resolving stream: {resolved['error']}") claim = ClaimDict.load_dict(resolved['value']) fee_amount, fee_address = None, None if claim.has_fee: fee_amount = round(exchange_rate_manager.convert_currency( claim.source_fee.currency, "LBC", claim.source_fee.amount ), 5) fee_address = claim.source_fee.address.decode() outpoint = f"{resolved['txid']}:{resolved['nout']}" existing = self.get_filtered_streams(outpoint=outpoint) if not existing: existing.extend(self.get_filtered_streams(sd_hash=claim.source_hash.decode())) if existing and existing[0].claim_id != resolved['claim_id']: raise Exception(f"stream for {existing[0].claim_id} collides with existing " f"download {resolved['claim_id']}") elif not existing: existing.extend(self.get_filtered_streams(claim_id=resolved['claim_id'])) if existing and existing[0].sd_hash != claim.source_hash.decode(): log.info("claim contains an update to a stream we have, downloading it") stream = await self.download_stream_from_claim( self.node, resolved, file_name, timeout, fee_amount, fee_address, False ) log.info("started new stream, deleting old one") await self.delete_stream(existing[0]) return stream elif existing: log.info("already have matching stream for %s", uri) stream = existing[0] await self.start_stream(stream) return stream else: stream = existing[0] await self.start_stream(stream) return stream log.info("download stream from %s", uri) return await self.download_stream_from_claim( self.node, resolved, file_name, timeout, fee_amount, fee_address )
def test_fail_to_validate_ecdsa_sig_for_altered_claim(self): cert = ClaimDict.generate_certificate(secp256k1_private_key, curve=SECP256k1) altered = ClaimDict.load_dict(example_010).sign(secp256k1_private_key, claim_address_1, claim_id_1, curve=SECP256k1, name='example', force_detached=True) original_serialization = altered.serialized sd_hash = altered['stream']['source']['source'] altered['stream']['source']['source'] = sd_hash[::-1] altered_serialization = altered.protobuf.SerializeToString() # keep signature, but replace serialization with the altered claim (check signature.py for slice sizes) altered_copy = ClaimDict.deserialize(original_serialization[:85] + altered_serialization) self.assertRaises(ecdsa.keys.BadSignatureError, altered_copy.validate_signature, claim_address_1, cert, 'example')
async def craft_claim(self, name, amount_dewies, claim_dict, address): # FIXME: this is here mostly because publish has defensive code for situations that happens accidentally # However, it still happens... So, let's reproduce them. claim = ClaimDict.load_dict(claim_dict) address = address or (await self.account.receiving.get_addresses(limit=1, only_usable=True))[0] tx = await Transaction.claim(name, claim, amount_dewies, address, [self.account], self.account) await self.broadcast(tx) await self.ledger.wait(tx) await self.generate(1) await self.ledger.wait(tx) return tx
def get_mock_wallet(sd_hash, storage): claim = { "address": "bYFeMtSL7ARuG1iMpjFyrnTe4oJHSAVNXF", "amount": "0.1", "claim_id": "c49566d631226492317d06ad7fdbe1ed32925124", "claim_sequence": 1, "decoded_claim": True, "depth": 1057, "effective_amount": "0.1", "has_signature": False, "height": 514081, "hex": "", "name": "33rpm", "nout": 0, "permanent_url": "33rpm#c49566d631226492317d06ad7fdbe1ed32925124", "supports": [], "txid": "81ac52662af926fdf639d56920069e0f63449d4cde074c61717cb99ddde40e3c", "value": { "claimType": "streamType", "stream": { "metadata": { "author": "", "description": "", "language": "en", "license": "None", "licenseUrl": "", "nsfw": False, "preview": "", "thumbnail": "", "title": "33rpm", "version": "_0_1_0" }, "source": { "contentType": "image/png", "source": sd_hash, "sourceType": "lbry_sd_hash", "version": "_0_0_1" }, "version": "_0_0_1" }, "version": "_0_0_1" } } claim_dict = ClaimDict.load_dict(claim['value']) claim['hex'] = binascii.hexlify(claim_dict.serialized).decode() async def mock_resolve(*args): await storage.save_claims([claim]) return {claim['permanent_url']: claim} mock_wallet = mock.Mock(spec=LbryWalletManager) mock_wallet.resolve = mock_resolve return mock_wallet, claim['permanent_url']
async def send_claim_to_address(self, claim_id: str, destination_address: str, amount: Optional[int], account=None): account = account or self.default_account claims = await account.ledger.db.get_utxos(claim_id=claim_id) if not claims: raise NameError(f"Claim not found: {claim_id}") tx = await Transaction.update( claims[0], ClaimDict.deserialize(claims[0].script.value['claim']), amount, destination_address.encode(), [account], account) await self.ledger.broadcast(tx) return tx
def _format_claim_response(outpoint, claim_id, name, amount, height, serialized, channel_id, address, claim_sequence): r = { "name": name, "claim_id": claim_id, "address": address, "claim_sequence": claim_sequence, "value": ClaimDict.deserialize(unhexlify(serialized)).claim_dict, "height": height, "amount": dewies_to_lbc(amount), "nout": int(outpoint.split(":")[1]), "txid": outpoint.split(":")[0], "channel_claim_id": channel_id, "channel_name": None } return r
async def test_new_signature_model_from_unserializable_claim(self): address1, address2 = await self.account.receiving.get_addresses( limit=2, only_usable=True) sendtxid1 = await self.blockchain.send_to_address(address1, 5) sendtxid2 = await self.blockchain.send_to_address(address2, 5) await self.blockchain.generate(1) await asyncio.wait([ self.on_transaction_id(sendtxid1), self.on_transaction_id(sendtxid2) ]) self.assertEqual(d2l(await self.account.get_balance()), '10.0') cert, key = generate_certificate() cert_tx = await Transaction.claim('@bar', cert, l2d('1.0'), address1, [self.account], self.account) original = ClaimDict.load_dict(example_claim_dict).serialized altered = original + b'\x00\x01\x02\x30\x50\x80\x99' # pretend this extra trash is from some unknown protobuf # manually sign signer = get_signer(SECP256k1).load_pem(key) signature = signer.sign( b'foo', decode_address(address1), altered, binascii.unhexlify(cert_tx.outputs[0].claim_id), ) detached_sig = Signature( NAMED_SECP256K1(signature, binascii.unhexlify(cert_tx.outputs[0].claim_id), altered)) claim_tx = await Transaction.claim('foo', detached_sig, l2d('1.0'), address1, [self.account], self.account) await self.broadcast(cert_tx) await self.broadcast(claim_tx) await self.ledger.wait(claim_tx) await self.blockchain.generate(1) await self.ledger.wait(claim_tx) response = await self.ledger.resolve(0, 10, 'lbry://@bar/foo') self.assertIn('lbry://@bar/foo', response) self.assertIn('claim', response['lbry://@bar/foo'])
async def claim_name(self, account, name, amount, claim_dict, certificate=None, claim_address=None): claim = ClaimDict.load_dict(claim_dict) if not claim_address: claim_address = await account.receiving.get_or_create_usable_address( ) if certificate: claim = claim.sign( certificate.private_key, claim_address, certificate.claim_id, curve=SECP256k1, name=name, force_detached= False # TODO: delete it and make True default everywhere when its out ) existing_claims = await account.get_claims( claim_name_type__any={ 'is_claim': 1, 'is_update': 1 }, # exclude is_supports claim_name=name) if len(existing_claims) == 0: tx = await Transaction.claim(name, claim, amount, claim_address, [account], account) elif len(existing_claims) == 1: tx = await Transaction.update(existing_claims[0], claim, amount, claim_address, [account], account) else: raise NameError( f"More than one other claim exists with the name '{name}'.") await account.ledger.broadcast(tx) await self.old_db.save_claims([ self._old_get_temp_claim_info(tx, tx.outputs[0], claim_address, claim_dict, name, dewies_to_lbc(amount)) ]) # TODO: release reserved tx outputs in case anything fails by this point return tx
def migrate_003_to_010(value): migrated_to_003 = LegacyMetadata(value) metadata = {"version": "_0_1_0"} for k in [ "author", "description", "language", "license", "nsfw", "thumbnail", "title", "preview" ]: if k in migrated_to_003: metadata.update({k: migrated_to_003[k]}) if 'license_url' in migrated_to_003: metadata['licenseUrl'] = migrated_to_003['license_url'] if "fee" in migrated_to_003: fee = migrated_to_003["fee"] currency = list(fee.keys())[0] amount = fee[currency]['amount'] address = fee[currency]['address'] metadata.update( dict( fee={ "currency": currency, "version": "_0_0_1", "amount": amount, "address": address })) source = { "source": migrated_to_003['sources']['lbry_sd_hash'], "contentType": migrated_to_003['content_type'], "sourceType": "lbry_sd_hash", "version": "_0_0_1" } migrated = { "version": "_0_0_1", "claimType": "streamType", "stream": { "version": "_0_0_1", "metadata": metadata, "source": source } } return ClaimDict.load_dict(migrated)
async def _download_stream_from_claim(self, node: 'Node', download_directory: str, claim_info: typing.Dict, file_name: typing.Optional[str] = None) -> typing.Optional[ManagedStream]: claim = ClaimDict.load_dict(claim_info['value']) downloader = StreamDownloader(self.loop, self.blob_manager, claim.source_hash.decode(), self.peer_timeout, self.peer_connect_timeout, download_directory, file_name, self.fixed_peers) try: downloader.download(node) await downloader.got_descriptor.wait() log.info("got descriptor %s for %s", claim.source_hash.decode(), claim_info['name']) except (asyncio.TimeoutError, asyncio.CancelledError): log.info("stream timeout") await downloader.stop() log.info("stopped stream") return if not await self.blob_manager.storage.stream_exists(downloader.sd_hash): await self.blob_manager.storage.store_stream(downloader.sd_blob, downloader.descriptor) if not await self.blob_manager.storage.file_exists(downloader.sd_hash): await self.blob_manager.storage.save_downloaded_file( downloader.descriptor.stream_hash, os.path.basename(downloader.output_path), download_directory, 0.0 ) await self.blob_manager.storage.save_content_claim( downloader.descriptor.stream_hash, f"{claim_info['txid']}:{claim_info['nout']}" ) stored_claim = StoredStreamClaim( downloader.descriptor.stream_hash, f"{claim_info['txid']}:{claim_info['nout']}", claim_info['claim_id'], claim_info['name'], claim_info['amount'], claim_info['height'], claim_info['hex'], claim.certificate_id, claim_info['address'], claim_info['claim_sequence'], claim_info.get('channel_name') ) stream = ManagedStream(self.loop, self.blob_manager, downloader.descriptor, download_directory, os.path.basename(downloader.output_path), downloader, ManagedStream.STATUS_RUNNING, stored_claim) self.streams.add(stream) try: await stream.downloader.wrote_bytes_event.wait() self.wait_for_stream_finished(stream) return stream except asyncio.CancelledError: await downloader.stop()
def _save_content_claim(transaction, claim_outpoint, stream_hash): # get the claim id and serialized metadata claim_info = transaction.execute( "select claim_id, serialized_metadata from claim where claim_outpoint=?", (claim_outpoint, )).fetchone() if not claim_info: raise Exception("claim not found") new_claim_id, claim = claim_info[0], ClaimDict.deserialize( binascii.unhexlify(claim_info[1])) # certificate claims should not be in the content_claim table if not claim.is_stream: raise Exception("claim does not contain a stream") # get the known sd hash for this stream known_sd_hash = transaction.execute( "select sd_hash from stream where stream_hash=?", (stream_hash, )).fetchone() if not known_sd_hash: raise Exception("stream not found") # check the claim contains the same sd hash if known_sd_hash[0].encode() != claim.source_hash: raise Exception("stream mismatch") # if there is a current claim associated to the file, check that the new claim is an update to it current_associated_content = transaction.execute( "select claim_outpoint from content_claim where stream_hash=?", (stream_hash, )).fetchone() if current_associated_content: current_associated_claim_id = transaction.execute( "select claim_id from claim where claim_outpoint=?", current_associated_content).fetchone()[0] if current_associated_claim_id != new_claim_id: raise Exception( f"mismatching claim ids when updating stream {current_associated_claim_id} vs {new_claim_id}" ) # update the claim associated to the file transaction.execute( "insert or replace into content_claim values (?, ?)", (stream_hash, claim_outpoint))
async def send_claim_to_address(self, claim_id: str, destination_address: str, amount: Optional[int], account=None): account = account or self.default_account claims = await account.get_claims( claim_name_type__any={ 'is_claim': 1, 'is_update': 1 }, # exclude is_supports claim_id=claim_id) if not claims: raise NameError(f"Claim not found: {claim_id}") if not amount: amount = claims[0].get_estimator(self.ledger).effective_amount tx = await Transaction.update( claims[0], ClaimDict.deserialize(claims[0].script.values['claim']), amount, destination_address.encode(), [account], account) await self.ledger.broadcast(tx) return tx