abandon claims and chris45 epic adventure

This commit is contained in:
Lex Berezhny 2018-07-31 22:59:51 -04:00 committed by Jack Robison
parent 10b34d6b33
commit 47bb634035
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
6 changed files with 125 additions and 29 deletions

View file

@ -1908,7 +1908,15 @@ class Daemon(AuthJSONRPCServer):
if nout is None and txid is not None: if nout is None and txid is not None:
raise Exception('Must specify nout') raise Exception('Must specify nout')
result = yield self.wallet.abandon_claim(claim_id, txid, nout) tx = yield self.wallet.abandon_claim(claim_id, txid, nout)
result = {
"success": True,
"txid": tx.id,
"nout": 0,
"tx": hexlify(tx.raw),
"fee": str(Decimal(tx.fee) / COIN),
"claim_id": claim_id
}
self.analytics_manager.send_claim_action('abandon') self.analytics_manager.send_claim_action('abandon')
defer.returnValue(result) defer.returnValue(result)

View file

@ -90,3 +90,6 @@ class Account(BaseAccount):
d = super().to_dict() d = super().to_dict()
d['certificates'] = self.certificates d['certificates'] = self.certificates
return d return d
def get_claim(self, claim_id):
return self.ledger.db.get_claim(self, claim_id)

View file

@ -1,6 +1,7 @@
from binascii import hexlify from binascii import hexlify
from twisted.internet import defer from twisted.internet import defer
from torba.basedatabase import BaseDatabase from torba.basedatabase import BaseDatabase
from torba.hash import TXRefImmutable
from .certificate import Certificate from .certificate import Certificate
@ -73,3 +74,23 @@ class WalletDatabase(BaseDatabase):
]) ])
defer.returnValue(certificates) defer.returnValue(certificates)
@defer.inlineCallbacks
def get_claim(self, account, claim_id):
utxos = yield self.db.runQuery(
"""
SELECT amount, script, txo.txid, position
FROM txo JOIN tx ON tx.txid=txo.txid
WHERE claim_id=? AND (is_claim OR is_update) AND txoid NOT IN (SELECT txoid FROM txi)
ORDER BY tx.height DESC LIMIT 1;
""", (claim_id,)
)
output_class = account.ledger.transaction_class.output_class
defer.returnValue([
output_class(
values[0],
output_class.script_class(values[1]),
TXRefImmutable.from_id(values[2]),
position=values[3]
) for values in utxos
])

View file

@ -184,6 +184,15 @@ class LbryWalletManager(BaseWalletManager):
"claim_sequence": -1, "claim_sequence": -1,
} }
@defer.inlineCallbacks
def abandon_claim(self, claim_id, txid, nout):
account = self.default_account
claim = yield account.get_claim(claim_id)
tx = yield Transaction.abandon(claim, [account], account)
yield account.ledger.broadcast(tx)
# TODO: release reserved tx outputs in case anything fails by this point
defer.returnValue(tx)
@defer.inlineCallbacks @defer.inlineCallbacks
def claim_new_channel(self, channel_name, amount): def claim_new_channel(self, channel_name, amount):
try: try:

View file

@ -50,4 +50,4 @@ class Transaction(BaseTransaction):
@classmethod @classmethod
def abandon(cls, utxo, funding_accounts, change_account): def abandon(cls, utxo, funding_accounts, change_account):
# type: (Output, List[BaseAccount], BaseAccount) -> defer.Deferred # type: (Output, List[BaseAccount], BaseAccount) -> defer.Deferred
return cls.liquidate([utxo], funding_accounts, change_account) return cls.liquidate(utxo, funding_accounts, change_account)

View file

@ -90,6 +90,7 @@ class CommandTestCase(IntegrationTestCase):
address = (await d2f(self.account.receiving.get_addresses(1, only_usable=True)))[0] address = (await d2f(self.account.receiving.get_addresses(1, only_usable=True)))[0]
sendtxid = await self.blockchain.send_to_address(address, 10) sendtxid = await self.blockchain.send_to_address(address, 10)
await self.confirm_tx(sendtxid) await self.confirm_tx(sendtxid)
await self.generate(5)
def wallet_maker(component_manager): def wallet_maker(component_manager):
self.wallet_component = WalletComponent(component_manager) self.wallet_component = WalletComponent(component_manager)
@ -136,68 +137,122 @@ class CommandTestCase(IntegrationTestCase):
return defer.Deferred.fromFuture(asyncio.ensure_future(self.generate(blocks))) return defer.Deferred.fromFuture(asyncio.ensure_future(self.generate(blocks)))
class CommonWorkflowTests(CommandTestCase): class EpicAdventuresOfChris45(CommandTestCase):
VERBOSE = False VERBOSE = False
@defer.inlineCallbacks @defer.inlineCallbacks
def test_user_creating_channel_and_publishing_file(self): def test_no_this_is_not_a_test_its_an_adventure(self):
# Chris45 is an avid user of LBRY and this is his story. It's fact and fiction
# and everything in between; it's also the setting of some record setting
# integration tests.
# User checks their balance. # Chris45 starts everyday by checking his balance.
result = yield self.daemon.jsonrpc_wallet_balance(include_unconfirmed=True) result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(result, 10) self.assertEqual(result, 10)
# "10 LBC, yippy! I can do a lot with that.", he thinks to himself,
# enthusiastically. But he is hungry so he goes into the kitchen
# to make himself a spamdwich.
# Decides to get a cool new channel. # While making the spamdwich he wonders... has anyone on LBRY
# registered the @spam channel yet? "I should do that!" he
# exclaims and goes back to his computer to do just that!
channel = yield self.daemon.jsonrpc_channel_new('@spam', 1) channel = yield self.daemon.jsonrpc_channel_new('@spam', 1)
self.assertTrue(channel['success']) self.assertTrue(channel['success'])
yield self.d_confirm_tx(channel['txid']) yield self.d_confirm_tx(channel['txid'])
# Check balance, include utxos with less than 6 confirmations (unconfirmed). # As the new channel claim travels through the intertubes and makes its
result = yield self.daemon.jsonrpc_wallet_balance(include_unconfirmed=True) # way into the mempool and then a block and then into the claimtrie,
self.assertEqual(result, 8.99) # Chris doesn't sit idly by: he checks his balance!
# Check confirmed balance, only includes utxos with 6+ confirmations.
result = yield self.daemon.jsonrpc_wallet_balance() result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(result, 0) self.assertEqual(result, 0)
# Add some confirmations (there is already 1 confirmation, so we add 5 to equal 6 total). # "Oh! No! It's all gone? Did I make a mistake in entering the amount?"
yield self.d_generate(5) # exclaims Chris, then he remembers there is a 6 block confirmation window
# to make sure the TX is really going to stay in the blockchain. And he only
# had one UTXO that morning.
# Check balance again after some confirmations, should be correct again. # To get the unconfirmed balance he has to pass the '--include-unconfirmed'
# flag to lbrynet:
result = yield self.daemon.jsonrpc_wallet_balance(include_unconfirmed=True)
self.assertEqual(result, 8.99)
# "Well, that's a relief." he thinks to himself as he exhales a sigh of relief.
# He waits for a block
yield self.d_generate(1)
# and checks the confirmed balance again.
result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(result, 0)
# Still zero.
# But it's only at 2 confirmations, so he waits another 3
yield self.d_generate(3)
# and checks again.
result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(result, 0)
# Still zero.
# Just one more confirmation
yield self.d_generate(1)
# and it should be 6 total, enough to get the correct balance!
result = yield self.daemon.jsonrpc_wallet_balance() result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(result, 8.99) self.assertEqual(result, 8.99)
# Like a Swiss watch (right niko?) the blockchain never disappoints! We're
# at 6 confirmations and the total is correct.
# Now lets publish a hello world file to the channel. # "What goes well with spam?" ponders Chris...
# "A hovercraft with eels!" he exclaims.
# "That's what goes great with spam!" he further confirms.
# And so, many hours later, Chris is finished writing his epic story
# about eels driving a hovercraft across the wetlands while eating spam
# and decides it's time to publish it to the @spam channel.
with tempfile.NamedTemporaryFile() as file: with tempfile.NamedTemporaryFile() as file:
file.write(b'hello world!') file.write(b'blah blah blah...')
file.write(b'[insert long story about eels driving hovercraft]')
file.write(b'yada yada yada!')
file.write(b'the end')
file.flush() file.flush()
claim = yield self.daemon.jsonrpc_publish( claim1 = yield self.daemon.jsonrpc_publish(
'hovercraft', 1, file_path=file.name, channel_name='@spam', channel_id=channel['claim_id'] 'hovercraft', 1, file_path=file.name, channel_name='@spam', channel_id=channel['claim_id']
) )
self.assertTrue(claim['success']) self.assertTrue(claim1['success'])
yield self.d_confirm_tx(claim['txid']) yield self.d_confirm_tx(claim1['txid'])
# Check unconfirmed balance. # He quickly checks the unconfirmed balance to make sure everything looks
# correct.
result = yield self.daemon.jsonrpc_wallet_balance(include_unconfirmed=True) result = yield self.daemon.jsonrpc_wallet_balance(include_unconfirmed=True)
self.assertEqual(round(result, 2), 7.97) self.assertEqual(round(result, 2), 7.97)
# Resolve our claim. # 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.ledger.resolve(0, 10, 'lbry://@spam/hovercraft')
self.assertIn('lbry://@spam/hovercraft', response) self.assertIn('lbry://@spam/hovercraft', response)
# A few confirmations before trying to spend again. # He goes to tell everyone about it and in the meantime 5 blocks are confirmed.
yield self.d_generate(5) yield self.d_generate(5)
# When he comes back he verifies the confirmed balance.
# Verify confirmed balance.
result = yield self.daemon.jsonrpc_wallet_balance() result = yield self.daemon.jsonrpc_wallet_balance()
self.assertEqual(round(result, 2), 7.97) self.assertEqual(round(result, 2), 7.97)
# Now lets update an existing claim. # As people start reading his story they discover some typos and notify
# Chris who explains in despair "Oh! Noooooos!" but then remembers
# "No big deal! I can update my claim." And so he updates his claim.
with tempfile.NamedTemporaryFile() as file: with tempfile.NamedTemporaryFile() as file:
file.write(b'hello world x2!') file.write(b'blah blah blah...')
file.write(b'[typo fixing sounds being made]')
file.write(b'yada yada yada!')
file.flush() file.flush()
claim = yield self.daemon.jsonrpc_publish( claim2 = yield self.daemon.jsonrpc_publish(
'hovercraft', 1, file_path=file.name, channel_name='@spam', channel_id=channel['claim_id'] 'hovercraft', 1, file_path=file.name, channel_name='@spam', channel_id=channel['claim_id']
) )
self.assertTrue(claim['success']) self.assertTrue(claim2['success'])
yield self.d_confirm_tx(claim['txid']) #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
# heart and a better ending. He takes down the story and begins the rewrite.
abandon = yield self.daemon.jsonrpc_claim_abandon(claim1['claim_id'])
self.assertTrue(abandon['success'])
yield self.d_confirm_tx(abandon['txid'])