tx.get_claim_id() -> txo.claim_id, claim update works now

channel_list encodes claim name and also added claim_id
fixed BlobManager foreign key error handling
This commit is contained in:
Lex Berezhny 2018-08-04 12:10:41 -04:00 committed by Jack Robison
parent 8ab4e3ca49
commit f41229cb5b
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
12 changed files with 104 additions and 64 deletions

View file

@ -101,7 +101,7 @@ class DiskBlobManager:
continue
if self._node_datastore is not None:
try:
self._node_datastore.completed_blobs.remove(blob_hash.decode('hex'))
self._node_datastore.completed_blobs.remove(unhexlify(blob_hash))
except KeyError:
pass
try:
@ -114,7 +114,7 @@ class DiskBlobManager:
try:
yield self.storage.delete_blobs_from_db(bh_to_delete_from_db)
except IntegrityError as err:
if err.message != "FOREIGN KEY constraint failed":
if str(err) != "FOREIGN KEY constraint failed":
raise err
@defer.inlineCallbacks

View file

@ -383,18 +383,21 @@ class Daemon(AuthJSONRPCServer):
log.exception)
self.analytics_manager.send_claim_action('publish')
nout = 0
script = tx.outputs[nout].script
log.info("Success! Published to lbry://%s txid: %s nout: %d", name, tx.id, nout)
defer.returnValue({
defer.returnValue(self._txo_to_response(tx, nout))
def _txo_to_response(self, tx, nout):
txo = tx.outputs[nout]
return {
"success": True,
"txid": tx.id,
"nout": nout,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": hexlify(tx.get_claim_id(0)[::-1]),
"value": hexlify(script.values['claim']),
"claim_address": self.ledger.hash160_to_address(script.values['pubkey_hash'])
})
"claim_id": txo.claim_id,
"value": hexlify(txo.claim).decode(),
"claim_address": self.ledger.hash160_to_address(txo.script.values['pubkey_hash'])
}
@defer.inlineCallbacks
def _resolve(self, *uris, **kwargs):
@ -1573,17 +1576,7 @@ class Daemon(AuthJSONRPCServer):
amount = int(amount * COIN)
tx = yield self.wallet.claim_new_channel(channel_name, amount)
self.wallet.save()
script = tx.outputs[0].script
result = {
"success": True,
"txid": tx.id,
"nout": 0,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": hexlify(tx.get_claim_id(0)[::-1]),
"value": hexlify(script.values['claim']),
"claim_address": self.ledger.hash160_to_address(script.values['pubkey_hash'])
}
result = self._txo_to_response(tx, 0)
self.analytics_manager.send_new_channel()
log.info("Claimed a new channel! Result: %s", result)
defer.returnValue(result)

View file

@ -1,7 +1,6 @@
import logging
import mimetypes
import os
from binascii import hexlify
from twisted.internet import defer
@ -48,7 +47,7 @@ class Publisher:
# check if we have a file already for this claim (if this is a publish update with a new stream)
old_stream_hashes = yield self.storage.get_old_stream_hashes_for_claim_id(
hexlify(tx.get_claim_id(0)[::-1]), self.lbry_file.stream_hash
tx.outputs[0].claim_id, self.lbry_file.stream_hash
)
if old_stream_hashes:
for lbry_file in filter(lambda l: l.stream_hash in old_stream_hashes,

View file

@ -1,5 +1,4 @@
import logging
from binascii import unhexlify
from twisted.internet import defer
@ -94,7 +93,8 @@ class Account(BaseAccount):
for utxo in utxos:
d = ClaimDict.deserialize(utxo.script.values['claim'])
channels.append({
'name': utxo.script.values['claim_name'],
'name': utxo.claim_name,
'claim_id': utxo.claim_id,
'txid': utxo.tx_ref.id,
'nout': utxo.position,
'have_certificate': utxo.ref.id in self.certificates

View file

@ -1,4 +1,3 @@
from binascii import hexlify
from twisted.internet import defer
from torba.basedatabase import BaseDatabase
from torba.hash import TXRefImmutable
@ -41,11 +40,8 @@ class WalletDatabase(BaseDatabase):
'is_support': txo.script.is_support_claim,
})
if txo.script.is_claim_involved:
row['claim_name'] = txo.script.values['claim_name'].decode()
if txo.script.is_update_claim or txo.script.is_support_claim:
row['claim_id'] = hexlify(txo.script.values['claim_id'][::-1])
elif txo.script.is_claim_name:
row['claim_id'] = hexlify(tx.get_claim_id(txo.position)[::-1])
row['claim_id'] = txo.claim_id
row['claim_name'] = txo.claim_name
return row
@defer.inlineCallbacks

View file

@ -1,6 +1,5 @@
import os
import json
from binascii import hexlify
from twisted.internet import defer
from torba.basemanager import BaseWalletManager
@ -164,7 +163,17 @@ class LbryWalletManager(BaseWalletManager):
claim = claim.sign(
certificate.private_key, claim_address, certificate.claim_id
)
tx = yield Transaction.claim(name.encode(), claim, amount, claim_address, [account], account)
existing_claims = yield account.get_unspent_outputs(include_claims=True, claim_name=name)
if len(existing_claims) == 0:
tx = yield Transaction.claim(
name, claim, amount, claim_address, [account], account
)
elif len(existing_claims) == 1:
tx = yield Transaction.update(
existing_claims[0], claim, amount, claim_address, [account], account
)
else:
raise NameError("More than one other claim exists with the name '{}'.".format(name))
yield account.ledger.broadcast(tx)
yield self.old_db.save_claims([self._old_get_temp_claim_info(
tx, tx.outputs[0], claim_address, claim_dict, name, amount
@ -173,10 +182,8 @@ class LbryWalletManager(BaseWalletManager):
defer.returnValue(tx)
def _old_get_temp_claim_info(self, tx, txo, address, claim_dict, name, bid):
if isinstance(address, memoryview):
address = str(address)
return {
"claim_id": hexlify(tx.get_claim_id(txo.position)).decode(),
"claim_id": txo.claim_id,
"name": name,
"amount": bid,
"address": address,
@ -211,7 +218,7 @@ class LbryWalletManager(BaseWalletManager):
account = self.default_account
address = yield account.receiving.get_or_create_usable_address()
cert, key = generate_certificate()
tx = yield Transaction.claim(channel_name.encode(), cert, amount, address, [account], account)
tx = yield Transaction.claim(channel_name, cert, amount, address, [account], account)
yield account.ledger.broadcast(tx)
account.add_certificate_private_key(tx.outputs[0].ref, key.decode())
# TODO: release reserved tx outputs in case anything fails by this point

View file

@ -63,6 +63,15 @@ class OutputScript(BaseOutputScript):
'pubkey_hash': pubkey_hash
})
@classmethod
def pay_update_claim_pubkey_hash(cls, claim_name, claim_id, claim, pubkey_hash):
return cls(template=cls.UPDATE_CLAIM_PUBKEY, values={
'claim_name': claim_name,
'claim_id': claim_id,
'claim': claim,
'pubkey_hash': pubkey_hash
})
@property
def is_claim_name(self):
return self.template.name.startswith('claim_name+')

View file

@ -1,8 +1,7 @@
import struct
from binascii import hexlify, unhexlify
from typing import List, Iterable # pylint: disable=unused-import
from twisted.internet import defer # pylint: disable=unused-import
from .account import Account # pylint: disable=unused-import
from torba.basetransaction import BaseTransaction, BaseInput, BaseOutput
from torba.hash import hash160
@ -11,15 +10,13 @@ from lbryschema.claim import ClaimDict # pylint: disable=unused-import
from .script import InputScript, OutputScript
def claim_id_hash(tx_hash, n):
return hash160(tx_hash + struct.pack('>I', n))
class Input(BaseInput):
script: InputScript
script_class = InputScript
class Output(BaseOutput):
script: OutputScript
script_class = OutputScript
def get_fee(self, ledger):
@ -28,9 +25,40 @@ class Output(BaseOutput):
name_fee = len(self.script.values['claim_name']) * ledger.fee_per_name_char
return max(name_fee, super().get_fee(ledger))
@property
def claim_id(self) -> str:
if self.script.is_claim_name:
claim_id = hash160(self.tx_ref.hash + struct.pack('>I', self.position))
elif self.script.is_update_claim or self.script.is_support_claim:
claim_id = self.script.values['claim_id']
else:
raise ValueError('No claim_id associated.')
return hexlify(claim_id[::-1]).decode()
@property
def claim_name(self) -> str:
if self.script.is_claim_involved:
return self.script.values['claim_name'].decode()
raise ValueError('No claim_name associated.')
@property
def claim(self) -> bytes:
if self.script.is_claim_involved:
return self.script.values['claim']
raise ValueError('No claim associated.')
@classmethod
def pay_claim_name_pubkey_hash(cls, amount, claim_name, claim, pubkey_hash):
script = cls.script_class.pay_claim_name_pubkey_hash(claim_name, claim, pubkey_hash)
def pay_claim_name_pubkey_hash(
cls, amount: int, claim_name: str, claim: bytes, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_claim_name_pubkey_hash(
claim_name.encode(), claim, pubkey_hash)
return cls(amount, script)
@classmethod
def pay_update_claim_pubkey_hash(
cls, amount: int, claim_name: str, claim_id: str, claim: bytes, pubkey_hash: bytes) -> 'Output':
script = cls.script_class.pay_update_claim_pubkey_hash(
claim_name.encode(), unhexlify(claim_id)[::-1], claim, pubkey_hash)
return cls(amount, script)
@ -39,19 +67,24 @@ class Transaction(BaseTransaction):
input_class = Input
output_class = Output
def get_claim_id(self, output_index):
output = self.outputs[output_index] # type: Output
assert output.script.is_claim_name, 'Not a name claim.'
return claim_id_hash(self.hash, output_index)
@classmethod
def claim(cls, name, meta, amount, holding_address, funding_accounts, change_account, spend=None):
# type: (bytes, ClaimDict, int, bytes, List[Account], Account) -> defer.Deferred
def claim(cls, name: str, meta: ClaimDict, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
claim_output = Output.pay_claim_name_pubkey_hash(
amount, name, meta.serialized, ledger.address_to_hash160(holding_address)
)
return cls.create(spend or [], [claim_output], funding_accounts, change_account)
return cls.create([], [claim_output], funding_accounts, change_account)
@classmethod
def update(cls, previous_claim: Output, meta: ClaimDict, amount: int, holding_address: bytes,
funding_accounts: List[Account], change_account: Account):
ledger = cls.ensure_all_have_same_ledger(funding_accounts, change_account)
updated_claim = Output.pay_update_claim_pubkey_hash(
amount, previous_claim.claim_name, previous_claim.claim_id,
meta.serialized, ledger.address_to_hash160(holding_address)
)
return cls.create([Input.spend(previous_claim)], [updated_claim], funding_accounts, change_account)
@classmethod
def abandon(cls, claims: Iterable[Output], funding_accounts: Iterable[Account], change_account: Account):

View file

@ -164,7 +164,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# Do we have it locally?
channels = yield self.daemon.jsonrpc_channel_list()
self.assertEqual(len(channels), 1)
self.assertEqual(channels[0]['name'], b'@spam')
self.assertEqual(channels[0]['name'], '@spam')
self.assertTrue(channels[0]['have_certificate'])
# As the new channel claim travels through the intertubes and makes its
@ -207,6 +207,11 @@ class EpicAdventuresOfChris45(CommandTestCase):
# Like a Swiss watch (right niko?) the blockchain never disappoints! We're
# at 6 confirmations and the total is correct.
# And is the channel resolvable and empty?
response = yield self.daemon.jsonrpc_resolve(uri='lbry://@spam')
self.assertIn('lbry://@spam', response)
self.assertIn('certificate', response['lbry://@spam'])
# "What goes well with spam?" ponders Chris...
# "A hovercraft with eels!" he exclaims.
# "That's what goes great with spam!" he further confirms.
@ -233,7 +238,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
# Also checks that his new story can be found on the blockchain before
# giving the link to all his friends.
response = yield self.ledger.resolve(0, 10, 'lbry://@spam/hovercraft')
response = yield self.daemon.jsonrpc_resolve(uri='lbry://@spam/hovercraft')
self.assertIn('lbry://@spam/hovercraft', response)
self.assertIn('claim', response['lbry://@spam/hovercraft'])
@ -255,7 +260,7 @@ class EpicAdventuresOfChris45(CommandTestCase):
'hovercraft', 1, file_path=file.name, channel_name='@spam', channel_id=channel['claim_id']
)
self.assertTrue(claim2['success'])
#self.assertEqual(claim2['claim_id'], claim1['claim_id'])
self.assertEqual(claim2['claim_id'], claim1['claim_id'])
yield self.d_confirm_tx(claim2['txid'])
# After some soul searching Chris decides that his story needs more
@ -264,6 +269,6 @@ class EpicAdventuresOfChris45(CommandTestCase):
self.assertTrue(abandon['success'])
yield self.d_confirm_tx(abandon['txid'])
# And checks that the claim doesn't resolve anymore.
response = yield self.ledger.resolve(0, 10, 'lbry://@spam/hovercraft')
# And now check that the claim doesn't resolve anymore.
response = yield self.daemon.jsonrpc_resolve(uri='lbry://@spam/hovercraft')
self.assertNotIn('claim', response['lbry://@spam/hovercraft'])

View file

@ -1,5 +1,4 @@
import asyncio
from binascii import hexlify
from orchstr8.testcase import IntegrationTestCase, d2f
from lbryschema.claim import ClaimDict
@ -58,10 +57,10 @@ class BasicTransactionTest(IntegrationTestCase):
self.assertEqual(round(await d2f(self.account.get_balance(0))/COIN, 1), 10.0)
cert, key = generate_certificate()
cert_tx = await d2f(Transaction.claim(b'@bar', cert, 1*COIN, address1, [self.account], self.account))
cert_tx = await d2f(Transaction.claim('@bar', cert, 1*COIN, address1, [self.account], self.account))
claim = ClaimDict.load_dict(example_claim_dict)
claim = claim.sign(key, address1, hexlify(cert_tx.get_claim_id(0)[::-1]))
claim_tx = await d2f(Transaction.claim(b'foo', claim, 1*COIN, address1, [self.account], self.account))
claim = claim.sign(key, address1, cert_tx.outputs[0].claim_id)
claim_tx = await d2f(Transaction.claim('foo', claim, 1*COIN, address1, [self.account], self.account))
await self.broadcast(cert_tx)
await self.broadcast(claim_tx)
@ -88,6 +87,5 @@ class BasicTransactionTest(IntegrationTestCase):
await self.blockchain.generate(1)
await self.on_transaction(abandon_tx)
# should not resolve, but does, why?
response = await d2f(self.ledger.resolve(0, 10, 'lbry://@bar/foo'))
self.assertNotIn('claim', response['lbry://@bar/foo'])

View file

@ -61,7 +61,7 @@ class BasicAccountingTests(LedgerTestCase):
balance = yield self.account.get_balance(0)
self.assertEqual(balance, 100)
tx = Transaction().add_outputs([Output.pay_claim_name_pubkey_hash(100, b'foo', b'', hash160)])
tx = Transaction().add_outputs([Output.pay_claim_name_pubkey_hash(100, 'foo', b'', hash160)])
yield self.ledger.db.save_transaction_io(
'insert', tx, 1, True, address, hash160, '{}:{}:'.format(tx.id, 1)
)

View file

@ -47,7 +47,7 @@ class TestSizeAndFeeEstimation(unittest.TestCase):
txo = get_output()
self.assertEqual(txo.size, 46)
self.assertEqual(txo.get_fee(self.ledger), 46 * FEE_PER_BYTE)
claim_name = b'verylongname'
claim_name = 'verylongname'
tx = get_claim_transaction(claim_name, b'0'*4000)
base_size = tx.size - tx.inputs[0].size - tx.outputs[0].size
txo = tx.outputs[0]
@ -56,7 +56,7 @@ class TestSizeAndFeeEstimation(unittest.TestCase):
self.assertEqual(txo.size, 4067)
self.assertEqual(txo.get_fee(self.ledger), len(claim_name) * FEE_PER_CHAR)
# fee based on total bytes is the larger fee
claim_name = b'a'
claim_name = 'a'
tx = get_claim_transaction(claim_name, b'0'*4000)
base_size = tx.size - tx.inputs[0].size - tx.outputs[0].size
txo = tx.outputs[0]