From d81502e191d46f4d06cbc08876c543f74c9c5b77 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Mon, 9 Jul 2018 17:04:59 -0400 Subject: [PATCH] unit test fixes, balance/utxos filters out claims, abandoning claims --- lbrynet/wallet/account.py | 8 ++ lbrynet/wallet/transaction.py | 5 + tests/integration/wallet/test_transactions.py | 50 ++++------ tests/unit/wallet/test_account.py | 99 +++++++++---------- tests/unit/wallet/test_ledger.py | 59 ++++++----- tox.ini | 5 +- 6 files changed, 114 insertions(+), 112 deletions(-) diff --git a/lbrynet/wallet/account.py b/lbrynet/wallet/account.py index 3f1630cfb..0cb1beffd 100644 --- a/lbrynet/wallet/account.py +++ b/lbrynet/wallet/account.py @@ -29,3 +29,11 @@ class Account(BaseAccount): return super(Account, self).get_balance( is_claim=0, is_update=0, is_support=0 ) + + def get_unspent_outputs(self, include_claims=False): + if include_claims: + return super(Account, self).get_unspent_outputs() + else: + return super(Account, self).get_unspent_outputs( + is_claim=0, is_update=0, is_support=0 + ) diff --git a/lbrynet/wallet/transaction.py b/lbrynet/wallet/transaction.py index fe1556572..94d2703c6 100644 --- a/lbrynet/wallet/transaction.py +++ b/lbrynet/wallet/transaction.py @@ -47,3 +47,8 @@ class Transaction(BaseTransaction): amount, name, hexlify(meta.serialized), ledger.address_to_hash160(holding_address) ) return cls.pay([claim_output], funding_accounts, change_account) + + @classmethod + def abandon(cls, utxo, funding_accounts, change_account): + # type: (Output, List[BaseAccount], BaseAccount) -> defer.Deferred + return cls.liquidate([utxo], funding_accounts, change_account) diff --git a/tests/integration/wallet/test_transactions.py b/tests/integration/wallet/test_transactions.py index 3b6e1cf6d..8cbe399ca 100644 --- a/tests/integration/wallet/test_transactions.py +++ b/tests/integration/wallet/test_transactions.py @@ -38,7 +38,7 @@ example_claim_dict = { } -class BasicTransactionTests(IntegrationTestCase): +class BasicTransactionTest(IntegrationTestCase): VERBOSE = True @@ -61,49 +61,33 @@ class BasicTransactionTests(IntegrationTestCase): cert_tx = await d2f(Transaction.claim(b'@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))) - tx = await d2f(Transaction.claim(b'foo', claim, 1*COIN, address1, [self.account], self.account)) + claim_tx = await d2f(Transaction.claim(b'foo', claim, 1*COIN, address1, [self.account], self.account)) await self.broadcast(cert_tx) - await self.broadcast(tx) + await self.broadcast(claim_tx) await asyncio.wait([ # mempool - self.on_transaction(tx), + self.on_transaction(claim_tx), self.on_transaction(cert_tx), ]) await self.blockchain.generate(1) await asyncio.wait([ # confirmed - self.on_transaction(tx), + self.on_transaction(claim_tx), self.on_transaction(cert_tx), ]) - self.assertEqual(round(await self.get_balance(self.account)/COIN, 1), 10.0) + self.assertEqual(round(await d2f(self.account.get_balance())/COIN, 1), 8.0) + self.assertEqual(round(await d2f(self.account.get_balance(True))/COIN, 1), 10.0) - header = self.ledger.headers[len(self.ledger.headers)-1] - response = await d2f(self.ledger.resolve(self.ledger.headers._hash_header(header), 'lbry://@bar/foo')) + response = await d2f(self.ledger.resolve('lbry://@bar/foo')) self.assertIn('lbry://@bar/foo', response) + claim_txo = claim_tx.outputs[0] + claim_txo.txoid = await d2f(self.ledger.db.get_txoid_for_txo(claim_txo)) + abandon_tx = await d2f(Transaction.abandon(claim_txo, [self.account], self.account)) + await self.broadcast(abandon_tx) + await self.on_transaction(abandon_tx) + await self.blockchain.generate(1) + await self.on_transaction(abandon_tx) -#class AbandonClaimLookup(IntegrationTestCase): -# -# async def skip_test_abandon_claim(self): -# address = yield self.lbry.wallet.get_least_used_address() -# yield self.lbrycrd.sendtoaddress(address, 0.0003 - 0.0000355) -# yield self.lbrycrd.generate(1) -# yield self.lbry.wallet.update_balance() -# yield threads.deferToThread(time.sleep, 5) -# print(self.lbry.wallet.get_balance()) -# claim = yield self.lbry.wallet.claim_new_channel('@test', 0.000096) -# yield self.lbrycrd.generate(1) -# print('='*10 + 'CLAIM' + '='*10) -# print(claim) -# yield self.lbrycrd.decoderawtransaction(claim['tx']) -# abandon = yield self.lbry.wallet.abandon_claim(claim['claim_id'], claim['txid'], claim['nout']) -# print('='*10 + 'ABANDON' + '='*10) -# print(abandon) -# yield self.lbrycrd.decoderawtransaction(abandon['tx']) -# yield self.lbrycrd.generate(1) -# yield self.lbrycrd.getrawtransaction(abandon['txid']) -# -# yield self.lbry.wallet.update_balance() -# yield threads.deferToThread(time.sleep, 5) -# print('='*10 + 'FINAL BALANCE' + '='*10) -# print(self.lbry.wallet.get_balance()) + response = await d2f(self.ledger.resolve('lbry://@bar/foo')) + self.assertIn('lbry://@bar/foo', response) diff --git a/tests/unit/wallet/test_account.py b/tests/unit/wallet/test_account.py index 1abbd1bd1..125bb7b77 100644 --- a/tests/unit/wallet/test_account.py +++ b/tests/unit/wallet/test_account.py @@ -1,62 +1,68 @@ from twisted.trial import unittest +from twisted.internet import defer -from lbrynet.wallet import LBC -from lbrynet.wallet.manager import LbryWalletManager -from torba.wallet import Account +from lbrynet.wallet.ledger import MainNetLedger +from lbrynet.wallet.account import Account class TestAccount(unittest.TestCase): def setUp(self): - ledger = LbryWalletManager().get_or_create_ledger(LBC.get_id()) - self.coin = LBC(ledger) + self.ledger = MainNetLedger(db=MainNetLedger.database_class(':memory:')) + return self.ledger.db.start() + @defer.inlineCallbacks def test_generate_account(self): - account = Account.generate(self.coin, u'lbryum') - self.assertEqual(account.coin, self.coin) + account = Account.generate(self.ledger, u'lbryum') + self.assertEqual(account.ledger, self.ledger) self.assertIsNotNone(account.seed) - self.assertEqual(account.public_key.coin, self.coin) + self.assertEqual(account.public_key.ledger, self.ledger) self.assertEqual(account.private_key.public_key, account.public_key) - self.assertEqual(len(account.receiving_keys.child_keys), 0) - self.assertEqual(len(account.receiving_keys.addresses), 0) - self.assertEqual(len(account.change_keys.child_keys), 0) - self.assertEqual(len(account.change_keys.addresses), 0) + self.assertEqual(account.public_key.ledger, self.ledger) + self.assertEqual(account.private_key.public_key, account.public_key) - account.ensure_enough_addresses() - self.assertEqual(len(account.receiving_keys.child_keys), 20) - self.assertEqual(len(account.receiving_keys.addresses), 20) - self.assertEqual(len(account.change_keys.child_keys), 6) - self.assertEqual(len(account.change_keys.addresses), 6) + addresses = yield account.receiving.get_addresses() + self.assertEqual(len(addresses), 0) + addresses = yield account.change.get_addresses() + self.assertEqual(len(addresses), 0) + yield account.ensure_address_gap() + + addresses = yield account.receiving.get_addresses() + self.assertEqual(len(addresses), 20) + addresses = yield account.change.get_addresses() + self.assertEqual(len(addresses), 6) + + @defer.inlineCallbacks def test_generate_account_from_seed(self): account = Account.from_seed( - self.coin, + self.ledger, u"carbon smart garage balance margin twelve chest sword toast envelope bottom stomach ab" u"sent", u"lbryum" ) self.assertEqual( account.private_key.extended_key_string(), - b'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB' - b'wwbRafEeA1ZXL69U2egM4QJdq' + b'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7DRNLEoB8' + b'HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe' ) self.assertEqual( account.public_key.extended_key_string(), - b'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK' - b'Ea5aoCNRBAhjT5NPLV6hXtvEi' + b'xpub661MyMwAqRbcGWtPvbWh9sc2BCfw2cTeVDYF23o3N1t6UZ5wv3EMmDgp66FxH' + b'uDtWdft3B5eL5xQtyzAtkdmhhC95gjRjLzSTdkho95asu9' ) - self.assertEqual( - account.receiving_keys.generate_next_address(), - b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx' - ) - private_key = account.get_private_key_for_address(b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx') + address = yield account.receiving.ensure_address_gap() + self.assertEqual(address[0], b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx') + + private_key = yield self.ledger.get_private_key_for_address(b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx') self.assertEqual( private_key.extended_key_string(), - b'LprvXTnmVLXGKvRGo2ihBE6LJ771G3VVpAx2zhTJvjnx5P3h6iZ4VJX8PvwTcgzJZ1hqXX61Wpn4pQoP6n2wgp' - b'S8xjzCM6H2uGzCXuAMy5H9vtA' + b'xprv9vwXVierUTT4hmoe3dtTeBfbNv1ph2mm8RWXARU6HsZjBaAoFaS2FRQu4fptR' + b'AyJWhJW42dmsEaC1nKnVKKTMhq3TVEHsNj1ca3ciZMKktT' ) - self.assertIsNone(account.get_private_key_for_address(b'BcQjRlhDOIrQez1WHfz3whnB33Bp34sUgX')) + private_key = yield self.ledger.get_private_key_for_address(b'BcQjRlhDOIrQez1WHfz3whnB33Bp34sUgX') + self.assertIsNone(private_key) def test_load_and_save_account(self): account_data = { @@ -65,34 +71,17 @@ class TestAccount(unittest.TestCase): "h absent", 'encrypted': False, 'private_key': - 'LprvXPsFZUGgrX1X9HiyxABZSf6hWJK7kHv4zGZRyyiHbBq5Wu94cE1DMvttnpLYReTPNW4eYwX9dWMvTz3PrB' - 'wwbRafEeA1ZXL69U2egM4QJdq', + 'xprv9s21ZrQH143K42ovpZygnjfHdAqSd9jo7zceDfPRogM7bkkoNVv7DRNLEoB8' + 'HoirMgH969NrgL8jNzLEegqFzPRWM37GXd4uE8uuRkx4LAe', 'public_key': - 'Lpub2hkYkGHXktBhLpwUhKKogyuJ1M7Gt9EkjFTVKyDqZiZpWdhLuCoT1eKDfXfysMFfG4SzfXXcA2SsHzrjHK' - 'Ea5aoCNRBAhjT5NPLV6hXtvEi', + 'xpub661MyMwAqRbcGWtPvbWh9sc2BCfw2cTeVDYF23o3N1t6UZ5wv3EMmDgp66FxH' + 'uDtWdft3B5eL5xQtyzAtkdmhhC95gjRjLzSTdkho95asu9', 'receiving_gap': 10, - 'receiving_keys': [ - '02c68e2d1cf85404c86244ffa279f4c5cd00331e996d30a86d6e46480e3a9220f4', - '03c5a997d0549875d23b8e4bbc7b4d316d962587483f3a2e62ddd90a21043c4941' - ], + 'receiving_maximum_use_per_address': 2, 'change_gap': 10, - 'change_keys': [ - '021460e8d728eee325d0d43128572b2e2bacdc027e420451df100cf9f2154ea5ab' - ] + 'change_maximum_use_per_address': 2, } - account = Account.from_dict(self.coin, account_data) - - self.assertEqual(len(account.receiving_keys.addresses), 2) - self.assertEqual( - account.receiving_keys.addresses[0], - b'bCqJrLHdoiRqEZ1whFZ3WHNb33bP34SuGx' - ) - self.assertEqual(len(account.change_keys.addresses), 1) - self.assertEqual( - account.change_keys.addresses[0], - b'bFpHENtqugKKHDshKFq2Mnb59Y2bx4vKgL' - ) - - account_data['coin'] = 'lbc_mainnet' + account = Account.from_dict(self.ledger, account_data) + account_data['ledger'] = 'lbc_mainnet' self.assertDictEqual(account_data, account.to_dict()) diff --git a/tests/unit/wallet/test_ledger.py b/tests/unit/wallet/test_ledger.py index fa1b96e32..8a8b5a446 100644 --- a/tests/unit/wallet/test_ledger.py +++ b/tests/unit/wallet/test_ledger.py @@ -1,38 +1,39 @@ -import shutil -import tempfile from twisted.internet import defer from twisted.trial import unittest from lbrynet import conf -from lbrynet.database.storage import SQLiteStorage +from lbrynet.wallet.account import Account from lbrynet.wallet.transaction import Transaction, Output, Input -from lbrynet.wallet.coin import LBC -from lbrynet.wallet.manager import LbryWalletManager -from torba.baseaccount import Account +from lbrynet.wallet.ledger import MainNetLedger from torba.wallet import Wallet +class MockHeaders: + def __init__(self, ledger): + self.ledger = ledger + self.height = 1 + + def __len__(self): + return self.height + + def __getitem__(self, height): + return {'merkle_root': 'abcd04'} + + class LedgerTestCase(unittest.TestCase): - @defer.inlineCallbacks def setUp(self): conf.initialize_settings(False) - self.db_dir = tempfile.mkdtemp() - self.storage = SQLiteStorage(self.db_dir) - yield self.storage.setup() - self.manager = LbryWalletManager(self.storage) - self.ledger = self.manager.get_or_create_ledger(LBC.get_id()) - self.coin = LBC(self.ledger) - self.wallet = Wallet('Main', [self.coin], [Account.from_seed( - self.coin, u'carbon smart garage balance margin twelve chest sword toast envelope botto' - u'm stomach absent', u'lbryum' + self.ledger = MainNetLedger(db=MainNetLedger.database_class(':memory:'), headers_class=MockHeaders) + self.wallet = Wallet('Main', [Account.from_seed( + self.ledger, u'carbon smart garage balance margin twelve chest sword toast envelope botto' + u'm stomach absent', u'lbryum' )]) self.account = self.wallet.default_account - yield self.storage.add_account(self.account) + return self.ledger.db.start() @defer.inlineCallbacks def tearDown(self): - yield self.storage.stop() - shutil.rmtree(self.db_dir) + yield self.ledger.db.stop() class BasicAccountingTests(LedgerTestCase): @@ -44,11 +45,25 @@ class BasicAccountingTests(LedgerTestCase): @defer.inlineCallbacks def test_balance(self): - tx = Transaction().add_outputs([Output.pay_pubkey_hash(100, b'abc1')]) - yield self.storage.add_tx_output(self.account, tx.outputs[0]) - balance = yield self.storage.get_balance_for_account(self.account) + 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, 1, True, address, hash160, '{}:{}:'.format(tx.hex_id, 1) + ) + balance = yield self.account.get_balance() self.assertEqual(balance, 100) + tx = Transaction().add_outputs([Output.pay_claim_name_pubkey_hash(100, b'foo', b'', hash160)]) + yield self.ledger.db.save_transaction_io( + 'insert', tx, 1, True, address, hash160, '{}:{}:'.format(tx.hex_id, 1) + ) + balance = yield self.account.get_balance() + self.assertEqual(balance, 100) # claim names don't count towards balance + balance = yield self.account.get_balance(include_claims=True) + self.assertEqual(balance, 200) + @defer.inlineCallbacks def test_get_utxo(self): tx1 = Transaction().add_outputs([Output.pay_pubkey_hash(100, b'abc1')]) diff --git a/tox.ini b/tox.ini index 0a83f2d2a..2794fbed0 100644 --- a/tox.ini +++ b/tox.ini @@ -14,9 +14,10 @@ extras = test changedir = {toxinidir}/tests setenv = HOME=/tmp + PYTHONHASHSEED=0 integration: LEDGER=lbrynet.wallet commands = unit: pylint lbrynet - unit: coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial functional unit + unit: coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial unit functional integration: orchstr8 download - integration: coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_transactions + integration: coverage run -p --source={envsitepackagesdir}/lbrynet -m twisted.trial --reactor=asyncio integration.wallet.test_transactions.BasicTransactionTest