From d5beaa0937fdc790d6d56464a1aaaace746295e7 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Wed, 4 Jul 2018 22:16:02 -0400 Subject: [PATCH] jsonrpc_channel_new uses new wallet --- lbrynet/daemon/Daemon.py | 58 ++++++++---------- .../tests/integration/wallet/test_commands.py | 56 +++++++++++++++++ lbrynet/wallet/account.py | 9 ++- lbrynet/wallet/ledger.py | 2 + lbrynet/wallet/manager.py | 60 ++++++++----------- 5 files changed, 114 insertions(+), 71 deletions(-) create mode 100644 lbrynet/tests/integration/wallet/test_commands.py diff --git a/lbrynet/daemon/Daemon.py b/lbrynet/daemon/Daemon.py index 10b47d43e..d0985f42c 100644 --- a/lbrynet/daemon/Daemon.py +++ b/lbrynet/daemon/Daemon.py @@ -1,5 +1,3 @@ -# coding=utf-8 -import binascii import logging.handlers import mimetypes import os @@ -7,6 +5,9 @@ import requests import urllib import json import textwrap +import signal +import six +from binascii import hexlify, unhexlify, b2a_hex from copy import deepcopy from decimal import Decimal, InvalidOperation from twisted.web import server @@ -14,6 +15,8 @@ from twisted.internet import defer, reactor from twisted.internet.task import LoopingCall from twisted.python.failure import Failure +from torba.constants import COIN + import lbryschema from lbryschema.claim import ClaimDict from lbryschema.uri import parse_lbry_uri @@ -231,6 +234,10 @@ class Daemon(AuthJSONRPCServer): # TODO: delete this self.streams = {} + @property + def ledger(self): + return self.session.wallet.default_account.ledger + @defer.inlineCallbacks def setup(self): log.info("Starting lbrynet-daemon") @@ -511,7 +518,7 @@ class Daemon(AuthJSONRPCServer): @defer.inlineCallbacks def _get_lbry_file_dict(self, lbry_file, full_status=False): - key = binascii.b2a_hex(lbry_file.key) if lbry_file.key else None + key = b2a_hex(lbry_file.key) if lbry_file.key else None full_path = os.path.join(lbry_file.download_directory, lbry_file.file_name) mime_type = mimetypes.guess_type(full_path)[0] if os.path.isfile(full_path): @@ -1532,38 +1539,21 @@ class Daemon(AuthJSONRPCServer): 'claim_id' : (str) claim ID of the resulting claim } """ - - try: - parsed = parse_lbry_uri(channel_name) - if not parsed.is_channel: - raise Exception("Cannot make a new channel for a non channel name") - if parsed.path: - raise Exception("Invalid channel uri") - except (TypeError, URIParseError): - raise Exception("Invalid channel name") - if amount <= 0: - raise Exception("Invalid amount") - - yield self.wallet.update_balance() - if amount >= self.wallet.get_balance(): - balance = yield self.wallet.get_max_usable_balance_for_claim(channel_name) - max_bid_amount = balance - MAX_UPDATE_FEE_ESTIMATE - if balance <= MAX_UPDATE_FEE_ESTIMATE: - raise InsufficientFundsError( - "Insufficient funds, please deposit additional LBC. Minimum additional LBC needed {}" - .format(MAX_UPDATE_FEE_ESTIMATE - balance)) - elif amount > max_bid_amount: - raise InsufficientFundsError( - "Please wait for any pending bids to resolve or lower the bid value. " - "Currently the maximum amount you can specify for this channel is {}" - .format(max_bid_amount) - ) - - result = yield self.wallet.claim_new_channel(channel_name, amount) + tx = yield self.wallet.claim_new_channel(channel_name, amount) + script = tx.outputs[0].script + result = { + "success": True, + "txid": tx.hex_id.decode(), + "nout": 0, + "tx": hexlify(tx.raw), + "fee": str(Decimal(tx.fee) / COIN), + "claim_id": tx.get_claim_id(0), + "value": hexlify(script.values['claim']), + "claim_address": self.ledger.hash160_to_address(script.values['pubkey_hash']) + } self.analytics_manager.send_new_channel() log.info("Claimed a new channel! Result: %s", result) - response = yield self._render_response(result) - defer.returnValue(response) + defer.returnValue(result) @requires(WALLET_COMPONENT) @defer.inlineCallbacks @@ -2628,7 +2618,7 @@ class Daemon(AuthJSONRPCServer): if not utils.is_valid_blobhash(blob_hash): raise Exception("invalid blob hash") - finished_deferred = self.dht_node.iterativeFindValue(binascii.unhexlify(blob_hash)) + finished_deferred = self.dht_node.iterativeFindValue(unhexlify(blob_hash)) def trap_timeout(err): err.trap(defer.TimeoutError) diff --git a/lbrynet/tests/integration/wallet/test_commands.py b/lbrynet/tests/integration/wallet/test_commands.py new file mode 100644 index 000000000..46a48050f --- /dev/null +++ b/lbrynet/tests/integration/wallet/test_commands.py @@ -0,0 +1,56 @@ +import types + +from orchstr8.testcase import IntegrationTestCase, d2f +from torba.constants import COIN + +import lbryschema +lbryschema.BLOCKCHAIN_NAME = 'lbrycrd_regtest' + +from lbrynet import conf as lbry_conf +from lbrynet.daemon.Daemon import Daemon +from lbrynet.wallet.manager import LbryWalletManager + + +class FakeAnalytics: + def send_new_channel(self): + pass + + +class CommandTestCase(IntegrationTestCase): + + WALLET_MANAGER = LbryWalletManager + + async def setUp(self): + await super().setUp() + + lbry_conf.settings = None + lbry_conf.initialize_settings(load_conf_file=False) + lbry_conf.settings['data_dir'] = self.stack.wallet.data_path + lbry_conf.settings['lbryum_wallet_dir'] = self.stack.wallet.data_path + lbry_conf.settings['download_directory'] = self.stack.wallet.data_path + lbry_conf.settings['use_upnp'] = False + lbry_conf.settings['blockchain_name'] = 'lbrycrd_regtest' + lbry_conf.settings['lbryum_servers'] = [('localhost', 50001)] + lbry_conf.settings['known_dht_nodes'] = [] + lbry_conf.settings.node_id = None + + await d2f(self.account.ensure_address_gap()) + address = (await d2f(self.account.receiving.get_usable_addresses(1)))[0] + sendtxid = await self.blockchain.send_to_address(address.decode(), 10) + await self.on_transaction_id(sendtxid) + await self.blockchain.generate(1) + await self.on_transaction_id(sendtxid) + self.daemon = Daemon(FakeAnalytics()) + self.daemon.session = types.SimpleNamespace() + self.daemon.session.wallet = self.manager + + +class DaemonCommandsTests(CommandTestCase): + + VERBOSE = True + + async def test_new_channel(self): + result = await d2f(self.daemon.jsonrpc_channel_new('@bar', 1*COIN)) + self.assertIn('txid', result) + await self.on_transaction_id(result['txid']) + diff --git a/lbrynet/wallet/account.py b/lbrynet/wallet/account.py index 5d44a094d..0db56699c 100644 --- a/lbrynet/wallet/account.py +++ b/lbrynet/wallet/account.py @@ -13,5 +13,12 @@ def generate_certificate(): class Account(BaseAccount): def __init__(self, *args, **kwargs): - super(BaseAccount, self).__init__(*args, **kwargs) + super(Account, self).__init__(*args, **kwargs) self.certificates = {} + + def add_certificate(self, claim_id, key): + assert claim_id not in self.certificates, 'Trying to add a duplicate certificate.' + self.certificates[claim_id] = key + + def get_certificate(self, claim_id): + return self.certificates[claim_id] diff --git a/lbrynet/wallet/ledger.py b/lbrynet/wallet/ledger.py index 66a2eb5e9..e45e87ce5 100644 --- a/lbrynet/wallet/ledger.py +++ b/lbrynet/wallet/ledger.py @@ -6,6 +6,7 @@ from torba.baseledger import BaseLedger from torba.baseheader import BaseHeaders, _ArithUint256 from torba.util import int_to_hex, rev_hex, hash_encode +from .account import Account from .network import Network from .database import WalletDatabase from .transaction import Transaction @@ -88,6 +89,7 @@ class MainNetLedger(BaseLedger): symbol = 'LBC' network_name = 'mainnet' + account_class = Account database_class = WalletDatabase headers_class = Headers network_class = Network diff --git a/lbrynet/wallet/manager.py b/lbrynet/wallet/manager.py index 10608f4f8..0bede8251 100644 --- a/lbrynet/wallet/manager.py +++ b/lbrynet/wallet/manager.py @@ -6,8 +6,12 @@ from torba.constants import COIN from torba.coinselection import CoinSelector from torba.manager import WalletManager as BaseWalletManager +from lbryschema.uri import parse_lbry_uri +from lbryschema.error import URIParseError from .ledger import MainNetLedger +from .account import generate_certificate +from .transaction import Transaction class BackwardsCompatibleNetwork: @@ -101,44 +105,28 @@ class LbryWalletManager(BaseWalletManager): return defer.succeed([]) def claim_name(self, name, amount, claim): - amount = int(amount * COIN) + pass + @defer.inlineCallbacks + def claim_new_channel(self, channel_name, amount): + try: + parsed = parse_lbry_uri(channel_name) + if not parsed.is_channel: + raise Exception("Cannot make a new channel for a non channel name") + if parsed.path: + raise Exception("Invalid channel uri") + except (TypeError, URIParseError): + raise Exception("Invalid channel name") + if amount <= 0: + raise Exception("Invalid amount") account = self.default_account - coin = account.coin - ledger = coin.ledger - - estimators = [ - txo.get_estimator(coin) for txo in ledger.get_unspent_outputs() - ] - - cost_of_output = coin.get_input_output_fee( - Output.pay_pubkey_hash(COIN, NULL_HASH) - ) - - selector = CoinSelector(estimators, amount, cost_of_output) - spendables = selector.select() - if not spendables: - raise ValueError('Not enough funds to cover this transaction.') - - claim_address = account.get_least_used_receiving_address() - outputs = [ - Output.pay_claim_name_pubkey_hash( - amount, name, claim, coin.address_to_hash160(claim_address) - ) - ] - - spent_sum = sum(s.effective_amount for s in spendables) - if spent_sum > amount: - change_address = account.get_least_used_change_address() - change_hash160 = coin.address_to_hash160(change_address) - outputs.append(Output.pay_pubkey_hash(spent_sum - amount, change_hash160)) - - tx = Transaction() \ - .add_inputs([s.txi for s in spendables]) \ - .add_outputs(outputs) \ - .sign(account) - - return tx + 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) + yield account.ledger.broadcast(tx) + account.add_certificate(tx.get_claim_id(0), key) + # TODO: release reserved tx outputs in case anything fails by this point + defer.returnValue(tx) class ReservedPoints: