def get_channel(self, title, amount, name='@foo'): claim = Claim() claim.channel.title = title channel = Output.pay_claim_name_pubkey_hash(amount, name, claim, b'abc') channel.generate_channel_private_key() return self._make_tx(channel)
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_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_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].tx.outputs[0].sign(channel) result[0].tx._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.save_transaction_io('insert', 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.save_transaction_io('insert', 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 test_balance(self): address = yield self.account.receiving.get_or_create_usable_address() hash160 = self.ledger.address_to_hash160(address) tx = Transaction().add_outputs([Output.pay_pubkey_hash(100, hash160)]) yield self.ledger.db.save_transaction_io('insert', tx, True, address, hash160, '{}:{}:'.format(tx.id, 1)) balance = yield self.account.get_balance(0) self.assertEqual(balance, 100) tx = Transaction().add_outputs( [Output.pay_claim_name_pubkey_hash(100, 'foo', b'', hash160)]) yield self.ledger.db.save_transaction_io('insert', tx, True, address, hash160, '{}:{}:'.format(tx.id, 1)) balance = yield self.account.get_balance(0) self.assertEqual(balance, 100) # claim names don't count towards balance balance = yield self.account.get_balance(0, include_claims=True) self.assertEqual(balance, 200)
def test_get_utxo(self): address = yield self.account.receiving.get_or_create_usable_address() hash160 = self.ledger.address_to_hash160(address) tx = Transaction().add_outputs([Output.pay_pubkey_hash(100, hash160)]) yield self.ledger.db.save_transaction_io('insert', tx, True, address, hash160, '{}:{}:'.format(tx.id, 1)) utxos = yield self.account.get_unspent_outputs() self.assertEqual(len(utxos), 1) tx = Transaction().add_inputs([Input.spend(utxos[0])]) yield self.ledger.db.save_transaction_io('insert', tx, True, address, hash160, '{}:{}:'.format(tx.id, 1)) balance = yield self.account.get_balance(0, include_claims=True) self.assertEqual(balance, 0) utxos = yield self.account.get_unspent_outputs() self.assertEqual(len(utxos), 0)
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')
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(0, 10, 'lbry://@bar/foo') self.assertIn('lbry://@bar/foo', response) self.assertIn('claim', response['lbry://@bar/foo']) 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(0, 10, 'lbry://@bar/foo') self.assertNotIn('claim', response['lbry://@bar/foo']) # checks for expected format in inexistent URIs response = await self.ledger.resolve(0, 10, 'lbry://404', 'lbry://@404') self.assertEqual('URI lbry://404 cannot be resolved', response['lbry://404']['error']) self.assertEqual('URI lbry://@404 cannot be resolved', response['lbry://@404']['error'])
def get_stream(): stream_txo = Output.pay_claim_name_pubkey_hash(CENT, 'foo', Claim(), b'abc') get_tx().add_outputs([stream_txo]) return stream_txo
def get_channel(): channel_txo = Output.pay_claim_name_pubkey_hash(CENT, '@foo', 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 validate_channel_signatures(self, height, new_claims, updated_claims, spent_claims): if not new_claims and not updated_claims and not spent_claims: return 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 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) all_channel_keys = {} if new_channel_keys or missing_channel_keys: all_channel_keys = dict(self.execute(*query( "SELECT claim_hash, public_key_bytes FROM claim", claim_hash__in=[ sqlite3.Binary(channel_hash) for channel_hash in set(new_channel_keys) | missing_channel_keys ] ))) 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': sqlite3.Binary(claim_hash), 'channel_hash': None, 'signature': None, 'signature_digest': None, 'is_channel_signature_valid': False } if claim.is_signed: update.update({ 'channel_hash': sqlite3.Binary(claim.signing_channel_hash), 'signature': sqlite3.Binary(txo.get_encoded_signature()), 'signature_digest': sqlite3.Binary(txo.get_signature_digest(self.ledger)) }) claim_updates.append(update) 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, [sqlite3.Binary(h) for h in changed_channel_keys]): if affected_claim['claim_hash'] not in signables: claim_updates.append({ 'claim_hash': sqlite3.Binary(affected_claim['claim_hash']), 'channel_hash': sqlite3.Binary(affected_claim['channel_hash']), 'signature': sqlite3.Binary(affected_claim['signature']), 'signature_digest': sqlite3.Binary(affected_claim['signature_digest']), 'is_channel_signature_valid': False }) for update in claim_updates: channel_pub_key = all_channel_keys.get(update['channel_hash']) if channel_pub_key and update['signature']: update['is_channel_signature_valid'] = Output.is_signature_valid( bytes(update['signature']), bytes(update['signature_digest']), channel_pub_key ) if claim_updates: self.db.executemany(f""" UPDATE claim SET channel_hash=:channel_hash, signature=:signature, signature_digest=:signature_digest, is_channel_signature_valid=:is_channel_signature_valid, channel_join=CASE WHEN is_channel_signature_valid AND :is_channel_signature_valid THEN channel_join WHEN :is_channel_signature_valid THEN {height} END, canonical_url=CASE WHEN is_channel_signature_valid AND :is_channel_signature_valid THEN canonical_url WHEN :is_channel_signature_valid 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.normalized = claim.normalized AND other_claim.channel_hash = :channel_hash AND other_claim.is_channel_signature_valid = 1), '#'||substr(claim_id, 1, 1) ) END WHERE claim_hash=:claim_hash; """, claim_updates) if spent_claims: self.execute( f""" UPDATE claim SET is_channel_signature_valid=0, channel_join=NULL, canonical_url=NULL WHERE channel_hash IN ({','.join('?' for _ in spent_claims)}) """, [sqlite3.Binary(cid) for cid in spent_claims] ) if channels: self.db.executemany( "UPDATE claim SET public_key_bytes=:public_key_bytes WHERE claim_hash=:claim_hash", [{ 'claim_hash': sqlite3.Binary(claim_hash), 'public_key_bytes': sqlite3.Binary(txo.claim.channel.public_key_bytes) } for claim_hash, txo in channels.items()] ) if all_channel_keys: self.db.executemany(f""" UPDATE claim SET claims_in_channel=( SELECT COUNT(*) FROM claim AS claim_in_channel WHERE claim_in_channel.channel_hash=claim.claim_hash AND claim_in_channel.is_channel_signature_valid ) WHERE claim_hash = ? """, [(sqlite3.Binary(channel_hash),) for channel_hash in all_channel_keys.keys()])
def get_claim_transaction(claim_name, claim=b''): return get_transaction( Output.pay_claim_name_pubkey_hash(CENT, claim_name, claim, NULL_HASH32))
def get_stream(self, title, amount, name='foo'): claim = Claim() claim.stream.title = title return self._make_tx( Output.pay_claim_name_pubkey_hash(amount, name, claim, b'abc'))
def get_support(self, tx, amount): claim = Transaction(tx[0].serialize()).outputs[0] return self._make_tx( Output.pay_support_pubkey_hash(amount, claim.claim_name, claim.claim_id, b'abc'))
def get_stream_abandon(self, tx): claim = Transaction(tx[0].serialize()).outputs[0] return self._make_tx(Output.pay_pubkey_hash(claim.amount, b'abc'), Input.spend(claim))
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_transaction(txo=None): return Transaction() \ .add_inputs([get_input()]) \ .add_outputs([txo or Output.pay_pubkey_hash(CENT, NULL_HASH32)])
def validate_channel_signatures(self, height, new_claims, updated_claims, spent_claims, 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: all_channel_keys = dict( self.execute( *query("SELECT claim_hash, public_key_bytes FROM claim", claim_hash__in=[ sqlite3.Binary(channel_hash) for channel_hash in set(new_channel_keys) | missing_channel_keys ]))) 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': sqlite3.Binary(claim_hash), 'channel_hash': None, 'signature': None, 'signature_digest': None, 'signature_valid': None } if claim.is_signed: update.update({ 'channel_hash': sqlite3.Binary(claim.signing_channel_hash), 'signature': sqlite3.Binary(txo.get_encoded_signature()), 'signature_digest': sqlite3.Binary(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, [sqlite3.Binary(h) for h in changed_channel_keys]): if affected_claim['claim_hash'] not in signables: claim_updates.append({ 'claim_hash': sqlite3.Binary(affected_claim['claim_hash']), 'channel_hash': sqlite3.Binary(affected_claim['channel_hash']), 'signature': sqlite3.Binary(affected_claim['signature']), 'signature_digest': sqlite3.Binary(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.db.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 THEN channel_join WHEN :signature_valid=1 THEN {height} END, canonical_url=CASE WHEN signature_valid=1 AND :signature_valid=1 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)}) """, [sqlite3.Binary(cid) for cid in spent_claims]) sub_timer.stop() sub_timer = timer.add_timer('update channels') sub_timer.start() if channels: self.db.executemany( """ UPDATE claim SET public_key_bytes=:public_key_bytes, public_key_hash=:public_key_hash WHERE claim_hash=:claim_hash""", [{ 'claim_hash': sqlite3.Binary(claim_hash), 'public_key_bytes': sqlite3.Binary(txo.claim.channel.public_key_bytes), 'public_key_hash': sqlite3.Binary( 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.db.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 = ? """, [(sqlite3.Binary(channel_hash), ) for channel_hash in all_channel_keys.keys()]) sub_timer.stop()