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:
parent
8ab4e3ca49
commit
f41229cb5b
12 changed files with 104 additions and 64 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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+')
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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'])
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Add table
Reference in a new issue