async def test_session_bloat_from_socket_timeout(self): await self.account.ensure_address_gap() 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') channel = Claim() channel_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), '@bar', channel, self.account.ledger.address_to_hash160(address1)) channel_txo.generate_channel_private_key() channel_txo.script.generate() channel_tx = await Transaction.create([], [channel_txo], [self.account], self.account) stream = Claim() stream.stream.description = "0" * 8000 stream_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), 'foo', stream, self.account.ledger.address_to_hash160(address1)) stream_tx = await Transaction.create([], [stream_txo], [self.account], self.account) stream_txo.sign(channel_txo) await stream_tx.sign([self.account]) self.paused_session.clear() self.resumed_session.clear() await self.broadcast(channel_tx) await self.broadcast(stream_tx) await asyncio.wait_for(self.paused_session.wait(), 2) self.assertEqual(1, len(self.session_manager.sessions)) real_sock = self.client_session.transport._extra.pop('socket') mock_sock = Mock(spec=socket.socket) for attr in dir(real_sock): if not attr.startswith('__'): setattr(mock_sock, attr, getattr(real_sock, attr)) def recv(*a, **kw): raise TimeoutError("[Errno 110] Connection timed out") mock_sock.recv = recv self.client_session.transport._sock = mock_sock self.client_session.transport._extra['socket'] = mock_sock self.assertFalse(self.resumed_session.is_set()) self.assertFalse(self.session_manager.session_event.is_set()) await self.session_manager.session_event.wait() self.assertEqual(0, len(self.session_manager.sessions))
def get_channel_update(self, channel, amount, key=b'a'): self._set_channel_key(channel, key) return self._make_tx( Output.pay_update_claim_pubkey_hash(amount, channel.claim_name, channel.claim_id, channel.claim, b'abc'), Input.spend(channel))
def get_support(self, tx, amount): claim = Transaction(tx[0].raw).outputs[0] return self._make_tx( Output.pay_support_pubkey_hash( amount, claim.claim_name, claim.claim_id, b'abc' ) )
async def test_sign(self): account = self.ledger.account_class.from_dict( self.ledger, Wallet(), { "seed": "carbon smart garage balance margin twelve chest sword toas" "t envelope bottom stomach absent" } ) await account.ensure_address_gap() address1, address2 = await account.receiving.get_addresses(limit=2) pubkey_hash1 = self.ledger.address_to_hash160(address1) pubkey_hash2 = self.ledger.address_to_hash160(address2) tx = Transaction() \ .add_inputs([Input.spend(get_output(int(2*COIN), pubkey_hash1))]) \ .add_outputs([Output.pay_pubkey_hash(int(1.9*COIN), pubkey_hash2)]) await tx.sign([account]) self.assertEqual( hexlify(tx.inputs[0].script.values['signature']), b'304402200dafa26ad7cf38c5a971c8a25ce7d85a076235f146126762296b1223c42ae21e022020ef9eeb8' b'398327891008c5c0be4357683f12cb22346691ff23914f457bf679601' )
def get_channel(self, title, amount, name='@foo', key=b'a'): claim = Claim() claim.channel.title = title channel = Output.pay_claim_name_pubkey_hash(amount, name, claim, b'abc') self._set_channel_key(channel, key) return self._make_tx(channel)
def get_stream(self, title, amount, name='foo', channel=None): claim = Claim() claim.stream.title = title result = self._make_tx(Output.pay_claim_name_pubkey_hash(amount, name, claim, b'abc')) if channel: result[0].outputs[0].sign(channel) result[0]._reset() return result
def get_stream_update(self, tx, amount): claim = Transaction(tx[0].serialize()).outputs[0] return self._make_tx( Output.pay_update_claim_pubkey_hash( amount, claim.claim_name, claim.claim_id, claim.claim, b'abc' ), Input.spend(claim) )
def get_repost(self, claim_id, amount, channel): claim = Claim() claim.repost.reference.claim_id = claim_id result = self._make_tx( Output.pay_claim_name_pubkey_hash(amount, 'repost', claim, b'abc')) result[0].outputs[0].sign(channel) result[0]._reset() return result
def verify(channel, data, signature, channel_hash=None): pieces = [ signature['signing_ts'].encode(), channel_hash or channel.claim_hash, data ] return Output.is_signature_valid( get_encoded_signature(signature['signature']), sha256(b''.join(pieces)), channel.claim.channel.public_key_bytes)
def get_stream_update(self, tx, amount, channel=None): stream = Transaction(tx[0].raw).outputs[0] result = self._make_tx( Output.pay_update_claim_pubkey_hash(amount, stream.claim_name, stream.claim_id, stream.claim, b'abc'), Input.spend(stream)) if channel: result[0].outputs[0].sign(channel) result[0]._reset() return result
async def test_balance(self): address = await self.account.receiving.get_or_create_usable_address() hash160 = self.ledger.address_to_hash160(address) tx = Transaction(is_verified=True)\ .add_outputs([Output.pay_pubkey_hash(100, hash160)]) await self.ledger.db.insert_transaction(tx) await self.ledger.db.save_transaction_io(tx, address, hash160, '{}:{}:'.format(tx.id, 1)) self.assertEqual(await self.account.get_balance(), 100) tx = Transaction(is_verified=True)\ .add_outputs([Output.pay_claim_name_pubkey_hash(100, 'foo', b'', hash160)]) await self.ledger.db.insert_transaction(tx) await self.ledger.db.save_transaction_io(tx, address, hash160, '{}:{}:'.format(tx.id, 1)) self.assertEqual(await self.account.get_balance(), 100) # claim names don't count towards balance self.assertEqual(await self.account.get_balance(include_claims=True), 200)
def is_comment_signed_by_channel(comment: dict, channel: Output): try: pieces = [ comment['signing_ts'].encode(), channel.claim_hash, comment['comment'].encode() ] return Output.is_signature_valid( get_encoded_signature(comment['signature']), sha256(b''.join(pieces)), channel.claim.channel.public_key_bytes) except KeyError: pass return False
async def _test_transaction(self, send_amount, address, inputs, change): tx = await Transaction.create([], [ Output.pay_pubkey_hash(send_amount, self.ledger.address_to_hash160(address)) ], [self.account], self.account) await self.ledger.broadcast(tx) input_amounts = [txi.amount for txi in tx.inputs] self.assertListEqual(inputs, input_amounts) self.assertEqual(len(inputs), len(tx.inputs)) self.assertEqual(2, len(tx.outputs)) self.assertEqual(send_amount, tx.outputs[0].amount) self.assertEqual(change, tx.outputs[1].amount) return tx
async def test_sending_and_receiving(self): account1, account2 = self.account, self.wallet.generate_account(self.ledger) await self.ledger.subscribe_account(account2) await self.assertBalance(account1, '0.0') await self.assertBalance(account2, '0.0') addresses = await account1.receiving.get_addresses() txids = await asyncio.gather(*( self.blockchain.send_to_address(address, 1.1) for address in addresses[:5] )) await asyncio.wait([self.on_transaction_id(txid) for txid in txids]) # mempool await self.blockchain.generate(1) await asyncio.wait([self.on_transaction_id(txid) for txid in txids]) # confirmed await self.assertBalance(account1, '5.5') await self.assertBalance(account2, '0.0') address2 = await account2.receiving.get_or_create_usable_address() tx = await Transaction.create( [], [Output.pay_pubkey_hash( coins_to_satoshis('2.0'), self.ledger.address_to_hash160(address2) )], [account1], account1 ) await self.broadcast(tx) await self.ledger.wait(tx) # mempool await self.blockchain.generate(1) await self.ledger.wait(tx) # confirmed await self.assertBalance(account1, '3.499802') await self.assertBalance(account2, '2.0') utxos = await self.account.get_utxos() tx = await Transaction.create( [Input.spend(utxos[0])], [], [account1], account1 ) await self.broadcast(tx) await self.ledger.wait(tx) # mempool await self.blockchain.generate(1) await self.ledger.wait(tx) # confirmed tx = (await account1.get_transactions(include_is_my_input=True, include_is_my_output=True))[1] self.assertEqual(satoshis_to_coins(tx.inputs[0].amount), '1.1') self.assertEqual(satoshis_to_coins(tx.inputs[1].amount), '1.1') self.assertEqual(satoshis_to_coins(tx.outputs[0].amount), '2.0') self.assertEqual(tx.outputs[0].get_address(self.ledger), address2) self.assertTrue(tx.outputs[0].is_internal_transfer) self.assertTrue(tx.outputs[1].is_internal_transfer)
def is_comment_signed_by_channel(comment: dict, channel: Output, sign_comment_id=False): if isinstance(channel, Output): try: signing_field = comment['comment_id'] if sign_comment_id else comment['comment'] pieces = [ comment['signing_ts'].encode(), cid2hash(comment['channel_id']), signing_field.encode() ] return Output.is_signature_valid( get_encoded_signature(comment['signature']), sha256(b''.join(pieces)), channel.claim.channel.public_key_bytes ) except KeyError: pass return False
def check(db_path, claim_id): db = sqlite3.connect(db_path) db.row_factory = sqlite3.Row claim = db.execute('select * from claim where claim_id=?', (claim_id,)).fetchone() if not claim: print('Could not find claim.') return channel = db.execute('select * from claim where claim_hash=?', (claim['channel_hash'],)).fetchone() if not channel: print('Could not find channel for this claim.') print(f"Claim: {claim['claim_name']}") print(f"Channel: {channel['claim_name']}") print(f"Signature: {hexlify(claim['signature']).decode()}") print(f"Digest: {hexlify(claim['signature_digest']).decode()}") print(f"Pubkey: {hexlify(channel['public_key_bytes']).decode()}") print("Valid: {}".format(Output.is_signature_valid( claim['signature'], claim['signature_digest'], channel['public_key_bytes'] )))
async def pay(self): while self.running: await asyncio.sleep(self.payment_period) features = await self.ledger.network.retriable_call( self.ledger.network.get_server_features) address = features['payment_address'] amount = str(features['daily_fee']) if not address or not amount: continue if not self.ledger.is_valid_address(address): self._on_payment_controller.add_error( ServerPaymentInvalidAddressError(address)) continue if self.wallet.is_locked: self._on_payment_controller.add_error( ServerPaymentWalletLockedError()) continue amount = lbc_to_dewies( features['daily_fee'] ) # check that this is in lbc and not dewies limit = lbc_to_dewies(self.max_fee) if amount > limit: self._on_payment_controller.add_error( ServerPaymentFeeAboveMaxAllowedError( features['daily_fee'], self.max_fee)) continue tx = await Transaction.create( [], [ Output.pay_pubkey_hash( amount, self.ledger.address_to_hash160(address)) ], self.wallet.get_accounts_or_all(None), self.wallet.get_account_or_default(None)) await self.ledger.broadcast(tx) if self.analytics_manager: await self.analytics_manager.send_credits_sent() self._on_payment_controller.add(tx)
async def test_get_utxo(self): address = yield self.account.receiving.get_or_create_usable_address() hash160 = self.ledger.address_to_hash160(address) tx = Transaction(is_verified=True)\ .add_outputs([Output.pay_pubkey_hash(100, hash160)]) await self.ledger.db.save_transaction_io('insert', tx, address, hash160, f'{tx.id}:1:') utxos = await self.account.get_utxos() self.assertEqual(len(utxos), 1) tx = Transaction(is_verified=True)\ .add_inputs([Input.spend(utxos[0])]) await self.ledger.db.save_transaction_io('insert', tx, address, hash160, f'{tx.id}:1:') self.assertEqual(await self.account.get_balance(include_claims=True), 0) utxos = await self.account.get_utxos() self.assertEqual(len(utxos), 0)
async def create_purchase_transaction( self, accounts: List[Account], txo: Output, exchange: ExchangeRateManager, override_max_key_fee=False): fee = txo.claim.stream.fee fee_amount = exchange.to_dewies(fee.currency, fee.amount) if not override_max_key_fee and self.config.max_key_fee: max_fee = self.config.max_key_fee max_fee_amount = exchange.to_dewies(max_fee['currency'], Decimal(max_fee['amount'])) if max_fee_amount and fee_amount > max_fee_amount: error_fee = f"{dewies_to_lbc(fee_amount)} LBC" if fee.currency != 'LBC': error_fee += f" ({fee.amount} {fee.currency})" error_max_fee = f"{dewies_to_lbc(max_fee_amount)} LBC" if max_fee['currency'] != 'LBC': error_max_fee += f" ({max_fee['amount']} {max_fee['currency']})" raise KeyFeeAboveMaxAllowedError( f"Purchase price of {error_fee} exceeds maximum " f"configured price of {error_max_fee}." ) fee_address = fee.address or txo.get_address(self.ledger) return await Transaction.purchase( txo.claim_id, fee_amount, fee_address, accounts, accounts[0] )
def get_transaction(txo=None): return Transaction() \ .add_inputs([get_input()]) \ .add_outputs([txo or Output.pay_pubkey_hash(CENT, NULL_HASH32)])
def get_channel(claim_name='@foo'): channel_txo = Output.pay_claim_name_pubkey_hash(CENT, claim_name, Claim(), b'abc') channel_txo.generate_channel_private_key() get_tx().add_outputs([channel_txo]) return channel_txo
def get_output(amount=CENT, pubkey_hash=NULL_HASH32): return Transaction() \ .add_outputs([Output.pay_pubkey_hash(amount, pubkey_hash)]) \ .outputs[0]
def get_stream(claim_name='foo'): stream_txo = Output.pay_claim_name_pubkey_hash(CENT, claim_name, Claim(), b'abc') get_tx().add_outputs([stream_txo]) return stream_txo
async def test_sqlite_coin_chooser(self): wallet_manager = WalletManager([self.wallet], {self.ledger.get_id(): self.ledger}) await self.generate(300) await self.assertBalance(self.account, '0.0') address = await self.account.receiving.get_or_create_usable_address() other_account = self.wallet.generate_account(self.ledger) other_address = await other_account.receiving.get_or_create_usable_address( ) self.ledger.coin_selection_strategy = 'sqlite' await self.ledger.subscribe_account(other_account) accepted = asyncio.ensure_future(self.on_address_update(address)) _ = await self.send_to_address_and_wait(address, 1.0) await accepted accepted = asyncio.ensure_future(self.on_address_update(address)) _ = await self.send_to_address_and_wait(address, 1.0) await accepted accepted = asyncio.ensure_future(self.on_address_update(address)) _ = await self.send_to_address_and_wait(address, 3.0) await accepted accepted = asyncio.ensure_future(self.on_address_update(address)) _ = await self.send_to_address_and_wait(address, 5.0) await accepted accepted = asyncio.ensure_future(self.on_address_update(address)) _ = await self.send_to_address_and_wait(address, 10.0) await accepted await self.assertBalance(self.account, '20.0') await self.assertSpendable( [99992600, 99992600, 299992600, 499992600, 999992600]) # send 1.5 lbc first_tx = await Transaction.create([], [ Output.pay_pubkey_hash( 150000000, self.ledger.address_to_hash160(other_address)) ], [self.account], self.account) self.assertEqual(2, len(first_tx.inputs)) self.assertEqual(2, len(first_tx.outputs)) self.assertEqual(100000000, first_tx.inputs[0].amount) self.assertEqual(100000000, first_tx.inputs[1].amount) self.assertEqual(150000000, first_tx.outputs[0].amount) self.assertEqual(49980200, first_tx.outputs[1].amount) await self.assertBalance(self.account, '18.0') await self.assertSpendable([299992600, 499992600, 999992600]) await wallet_manager.broadcast_or_release(first_tx, blocking=True) await self.assertSpendable([49972800, 299992600, 499992600, 999992600]) # 0.499, 3.0, 5.0, 10.0 await self.assertBalance(self.account, '18.499802') # send 1.5lbc again second_tx = await self._test_transaction(150000000, other_address, [49980200, 300000000], 199960400) await self.assertSpendable([499992600, 999992600]) # replicate cancelling the api call after the tx broadcast while ledger.wait'ing it e = asyncio.Event() real_broadcast = self.ledger.broadcast async def broadcast(tx): try: return await real_broadcast(tx) except lbry.wallet.rpc.jsonrpc.RPCError as err: # this is expected in tests where we try to double spend. if 'the transaction was rejected by network rules.' in str( err): pass else: raise err finally: e.set() self.ledger.broadcast = broadcast broadcast_task = asyncio.create_task( wallet_manager.broadcast_or_release(second_tx, blocking=True)) # wait for the broadcast to finish await e.wait() # cancel the api call broadcast_task.cancel() with self.assertRaises(asyncio.CancelledError): await broadcast_task # test if sending another 1.5 lbc will try to double spend the inputs from the cancelled tx tx1 = await self._test_transaction(150000000, other_address, [500000000], 349987600) await self.ledger.wait(tx1, timeout=1) # wait for the cancelled transaction too, so that it's in the database # needed to keep everything deterministic await self.ledger.wait(second_tx, timeout=1) await self.assertSpendable([199953000, 349980200, 999992600]) # spend deep into the mempool and see what else breaks tx2 = await self._test_transaction(150000000, other_address, [199960400], 49948000) await self.assertSpendable([349980200, 999992600]) await self.ledger.wait(tx2, timeout=1) await self.assertSpendable([49940600, 349980200, 999992600]) tx3 = await self._test_transaction(150000000, other_address, [49948000, 349987600], 249915800) await self.assertSpendable([999992600]) await self.ledger.wait(tx3, timeout=1) await self.assertSpendable([249908400, 999992600]) tx4 = await self._test_transaction(150000000, other_address, [249915800], 99903400) await self.assertSpendable([999992600]) await self.ledger.wait(tx4, timeout=1) await self.assertBalance(self.account, '10.999034') await self.assertSpendable([99896000, 999992600]) # spend more tx5 = await self._test_transaction(100000000, other_address, [99903400, 1000000000], 999883600) await self.assertSpendable([]) await self.ledger.wait(tx5, timeout=1) await self.assertSpendable([999876200]) await self.assertBalance(self.account, '9.998836')
async def test_variety_of_transactions_and_longish_history(self): await self.generate(300) await self.assertBalance(self.account, '0.0') addresses = await self.account.receiving.get_addresses() # send 10 coins to first 10 receiving addresses and then 10 transactions worth 10 coins each # to the 10th receiving address for a total of 30 UTXOs on the entire account for i in range(10): notification = asyncio.ensure_future( self.on_address_update(addresses[i])) _ = await self.send_to_address_and_wait(addresses[i], 10) await notification notification = asyncio.ensure_future( self.on_address_update(addresses[9])) _ = await self.send_to_address_and_wait(addresses[9], 10) await notification # use batching to reduce issues with send_to_address on cli await self.assertBalance(self.account, '200.0') self.assertEqual(20, await self.account.get_utxo_count()) # address gap should have increase by 10 to cover the first 10 addresses we've used up addresses = await self.account.receiving.get_addresses() self.assertEqual(30, len(addresses)) # there used to be a sync bug which failed to save TXIs between # daemon restarts, clearing cache replicates that behavior self.ledger._tx_cache.clear() # spend from each of the first 10 addresses to the subsequent 10 addresses txs = [] for address in addresses[10:20]: txs.append(await Transaction.create([], [ Output.pay_pubkey_hash(coins_to_satoshis('1.0'), self.ledger.address_to_hash160(address)) ], [self.account], self.account)) await asyncio.wait([self.broadcast(tx) for tx in txs]) await asyncio.wait([self.ledger.wait(tx) for tx in txs]) # verify that a previous bug which failed to save TXIs doesn't come back # this check must happen before generating a new block self.assertTrue( all([ tx.inputs[0].txo_ref.txo is not None for tx in await self.ledger.db.get_transactions( txid__in=[tx.id for tx in txs]) ])) await self.generate(1) await asyncio.wait([self.ledger.wait(tx) for tx in txs]) await self.assertBalance(self.account, '199.99876') # 10 of the UTXOs have been split into a 1 coin UTXO and a 9 UTXO change self.assertEqual(30, await self.account.get_utxo_count()) # spend all 30 UTXOs into a a 199 coin UTXO and change tx = await Transaction.create([], [ Output.pay_pubkey_hash( coins_to_satoshis('199.0'), self.ledger.address_to_hash160(addresses[-1])) ], [self.account], self.account) await self.broadcast(tx) await self.ledger.wait(tx) await self.generate(1) await self.ledger.wait(tx) self.assertEqual(2, await self.account.get_utxo_count()) # 199 + change await self.assertBalance(self.account, '199.99649')
def get_claim_transaction(claim_name, claim=b''): return get_transaction( Output.pay_claim_name_pubkey_hash(CENT, claim_name, claim, NULL_HASH32) )
async def test_creating_updating_and_abandoning_claim_with_channel(self): await self.account.ensure_address_gap() 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') channel = Claim() channel_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), '@bar', channel, self.account.ledger.address_to_hash160(address1)) channel_txo.generate_channel_private_key() channel_txo.script.generate() channel_tx = await Transaction.create([], [channel_txo], [self.account], self.account) stream = Claim() stream.stream.source.media_type = "video/mp4" stream_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), 'foo', stream, self.account.ledger.address_to_hash160(address1)) stream_tx = await Transaction.create([], [stream_txo], [self.account], self.account) stream_txo.sign(channel_txo) await stream_tx.sign([self.account]) await self.broadcast(channel_tx) await self.broadcast(stream_tx) await asyncio.wait([ # mempool self.ledger.wait(channel_tx), self.ledger.wait(stream_tx) ]) await self.blockchain.generate(1) await asyncio.wait([ # confirmed self.ledger.wait(channel_tx), self.ledger.wait(stream_tx) ]) self.assertEqual(d2l(await self.account.get_balance()), '7.985786') self.assertEqual( d2l(await self.account.get_balance(include_claims=True)), '9.985786') response = await self.ledger.resolve([], ['lbry://@bar/foo']) self.assertEqual(response['lbry://@bar/foo'].claim.claim_type, 'stream') abandon_tx = await Transaction.create( [Input.spend(stream_tx.outputs[0])], [], [self.account], self.account) await self.broadcast(abandon_tx) await self.ledger.wait(abandon_tx) await self.blockchain.generate(1) await self.ledger.wait(abandon_tx) response = await self.ledger.resolve([], ['lbry://@bar/foo']) self.assertIn('error', response['lbry://@bar/foo']) # checks for expected format in inexistent URIs response = await self.ledger.resolve([], ['lbry://404', 'lbry://@404']) self.assertEqual('lbry://404 did not resolve to a claim', response['lbry://404']['error']) self.assertEqual('lbry://@404 did not resolve to a claim', response['lbry://@404']['error'])
def get_abandon(self, tx): claim = Transaction(tx[0].raw).outputs[0] return self._make_tx(Output.pay_pubkey_hash(claim.amount, b'abc'), Input.spend(claim))
def validate_channel_signatures(self, height, new_claims, updated_claims, spent_claims, affected_channels, timer): if not new_claims and not updated_claims and not spent_claims: return sub_timer = timer.add_timer('segregate channels and signables') sub_timer.start() channels, new_channel_keys, signables = {}, {}, {} for txo in chain(new_claims, updated_claims): try: claim = txo.claim except: continue if claim.is_channel: channels[txo.claim_hash] = txo new_channel_keys[txo.claim_hash] = claim.channel.public_key_bytes else: signables[txo.claim_hash] = txo sub_timer.stop() sub_timer = timer.add_timer('make list of channels we need to lookup') sub_timer.start() missing_channel_keys = set() for txo in signables.values(): claim = txo.claim if claim.is_signed and claim.signing_channel_hash not in new_channel_keys: missing_channel_keys.add(claim.signing_channel_hash) sub_timer.stop() sub_timer = timer.add_timer('lookup missing channels') sub_timer.start() all_channel_keys = {} if new_channel_keys or missing_channel_keys or affected_channels: all_channel_keys = dict(self.execute(*query( "SELECT claim_hash, public_key_bytes FROM claim", claim_hash__in=set(new_channel_keys) | missing_channel_keys | affected_channels ))) sub_timer.stop() sub_timer = timer.add_timer('prepare for updating claims') sub_timer.start() changed_channel_keys = {} for claim_hash, new_key in new_channel_keys.items(): if claim_hash not in all_channel_keys or all_channel_keys[claim_hash] != new_key: all_channel_keys[claim_hash] = new_key changed_channel_keys[claim_hash] = new_key claim_updates = [] for claim_hash, txo in signables.items(): claim = txo.claim update = { 'claim_hash': claim_hash, 'channel_hash': None, 'signature': None, 'signature_digest': None, 'signature_valid': None } if claim.is_signed: update.update({ 'channel_hash': claim.signing_channel_hash, 'signature': txo.get_encoded_signature(), 'signature_digest': txo.get_signature_digest(self.ledger), 'signature_valid': 0 }) claim_updates.append(update) sub_timer.stop() sub_timer = timer.add_timer('find claims affected by a change in channel key') sub_timer.start() if changed_channel_keys: sql = f""" SELECT * FROM claim WHERE channel_hash IN ({','.join('?' for _ in changed_channel_keys)}) AND signature IS NOT NULL """ for affected_claim in self.execute(sql, changed_channel_keys.keys()): if affected_claim.claim_hash not in signables: claim_updates.append({ 'claim_hash': affected_claim.claim_hash, 'channel_hash': affected_claim.channel_hash, 'signature': affected_claim.signature, 'signature_digest': affected_claim.signature_digest, 'signature_valid': 0 }) sub_timer.stop() sub_timer = timer.add_timer('verify signatures') sub_timer.start() for update in claim_updates: channel_pub_key = all_channel_keys.get(update['channel_hash']) if channel_pub_key and update['signature']: update['signature_valid'] = Output.is_signature_valid( bytes(update['signature']), bytes(update['signature_digest']), channel_pub_key ) sub_timer.stop() sub_timer = timer.add_timer('update claims') sub_timer.start() if claim_updates: self.executemany(f""" UPDATE claim SET channel_hash=:channel_hash, signature=:signature, signature_digest=:signature_digest, signature_valid=:signature_valid, channel_join=CASE WHEN signature_valid=1 AND :signature_valid=1 AND channel_hash=:channel_hash THEN channel_join WHEN :signature_valid=1 THEN {height} END, canonical_url=CASE WHEN signature_valid=1 AND :signature_valid=1 AND channel_hash=:channel_hash THEN canonical_url WHEN :signature_valid=1 THEN (SELECT short_url FROM claim WHERE claim_hash=:channel_hash)||'/'|| claim_name||COALESCE( (SELECT shortest_id(other_claim.claim_id, claim.claim_id) FROM claim AS other_claim WHERE other_claim.signature_valid = 1 AND other_claim.channel_hash = :channel_hash AND other_claim.normalized = claim.normalized), '#'||substr(claim_id, 1, 1) ) END WHERE claim_hash=:claim_hash; """, claim_updates) sub_timer.stop() sub_timer = timer.add_timer('update claims affected by spent channels') sub_timer.start() if spent_claims: self.execute( f""" UPDATE claim SET signature_valid=CASE WHEN signature IS NOT NULL THEN 0 END, channel_join=NULL, canonical_url=NULL WHERE channel_hash IN ({','.join('?' for _ in spent_claims)}) """, spent_claims ) sub_timer.stop() sub_timer = timer.add_timer('update channels') sub_timer.start() if channels: self.executemany( """ UPDATE claim SET public_key_bytes=:public_key_bytes, public_key_hash=:public_key_hash WHERE claim_hash=:claim_hash""", [{ 'claim_hash': claim_hash, 'public_key_bytes': txo.claim.channel.public_key_bytes, 'public_key_hash': self.ledger.address_to_hash160( self.ledger.public_key_to_address(txo.claim.channel.public_key_bytes) ) } for claim_hash, txo in channels.items()] ) sub_timer.stop() sub_timer = timer.add_timer('update claims_in_channel counts') sub_timer.start() if all_channel_keys: self.executemany(f""" UPDATE claim SET claims_in_channel=( SELECT COUNT(*) FROM claim AS claim_in_channel WHERE claim_in_channel.signature_valid=1 AND claim_in_channel.channel_hash=claim.claim_hash ) WHERE claim_hash = ? """, [(channel_hash,) for channel_hash in all_channel_keys.keys()]) sub_timer.stop() sub_timer = timer.add_timer('update blocked claims list') sub_timer.start() if (self.blocking_channel_hashes.intersection(all_channel_keys) or self.filtering_channel_hashes.intersection(all_channel_keys)): self.update_blocked_and_filtered_claims() sub_timer.stop()
async def test_creating_updating_and_abandoning_claim_with_channel(self): await self.account.ensure_address_gap() address1, address2 = await self.account.receiving.get_addresses(limit=2, only_usable=True) notifications = asyncio.create_task(asyncio.wait( [asyncio.ensure_future(self.on_address_update(address1)), asyncio.ensure_future(self.on_address_update(address2))] )) await self.send_to_address_and_wait(address1, 5) await self.send_to_address_and_wait(address2, 5, 1) await notifications self.assertEqual(d2l(await self.account.get_balance()), '10.0') channel = Claim() channel_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), '@bar', channel, self.account.ledger.address_to_hash160(address1) ) channel_txo.set_channel_private_key( await self.account.generate_channel_private_key() ) channel_txo.script.generate() channel_tx = await Transaction.create([], [channel_txo], [self.account], self.account) stream = Claim() stream.stream.source.media_type = "video/mp4" stream_txo = Output.pay_claim_name_pubkey_hash( l2d('1.0'), 'foo', stream, self.account.ledger.address_to_hash160(address1) ) stream_tx = await Transaction.create([], [stream_txo], [self.account], self.account) stream_txo.sign(channel_txo) await stream_tx.sign([self.account]) notifications = asyncio.create_task(asyncio.wait( [asyncio.ensure_future(self.ledger.wait(channel_tx)), asyncio.ensure_future(self.ledger.wait(stream_tx))] )) await self.broadcast(channel_tx) await self.broadcast(stream_tx) await notifications notifications = asyncio.create_task(asyncio.wait( [asyncio.ensure_future(self.ledger.wait(channel_tx)), asyncio.ensure_future(self.ledger.wait(stream_tx))] )) await self.generate(1) await notifications self.assertEqual(d2l(await self.account.get_balance()), '7.985786') self.assertEqual(d2l(await self.account.get_balance(include_claims=True)), '9.985786') response = await self.ledger.resolve([], ['lbry://@bar/foo']) self.assertEqual(response['lbry://@bar/foo'].claim.claim_type, 'stream') abandon_tx = await Transaction.create([Input.spend(stream_tx.outputs[0])], [], [self.account], self.account) notify = asyncio.create_task(self.ledger.wait(abandon_tx)) await self.broadcast(abandon_tx) await notify notify = asyncio.create_task(self.ledger.wait(abandon_tx)) await self.generate(1) await notify response = await self.ledger.resolve([], ['lbry://@bar/foo']) self.assertIn('error', response['lbry://@bar/foo']) # checks for expected format in inexistent URIs response = await self.ledger.resolve([], ['lbry://404', 'lbry://@404', 'lbry://@404/404']) self.assertEqual('Could not find claim at "lbry://404".', response['lbry://404']['error']['text']) self.assertEqual('Could not find channel in "lbry://@404".', response['lbry://@404']['error']['text']) self.assertEqual('Could not find channel in "lbry://@404/404".', response['lbry://@404/404']['error']['text'])