new fund command and automatic account creation

This commit is contained in:
Lex Berezhny 2018-08-06 00:28:11 -04:00 committed by Jack Robison
parent 4165881d5f
commit 6a5d88a0d5
No known key found for this signature in database
GPG key ID: DF25C68FE0239BB2
5 changed files with 115 additions and 96 deletions

View file

@ -67,16 +67,9 @@ def normalize_value(x, key=None):
return True return True
if x.lower() == 'false': if x.lower() == 'false':
return False return False
if '.' in x: if x.isdigit():
try:
return float(x)
except ValueError:
# not a float
pass
try:
return int(x) return int(x)
except ValueError: return x
return x
def remove_brackets(key): def remove_brackets(key):

View file

@ -12,6 +12,7 @@ from twisted.web import server
from twisted.internet import defer, reactor from twisted.internet import defer, reactor
from twisted.internet.task import LoopingCall from twisted.internet.task import LoopingCall
from twisted.python.failure import Failure from twisted.python.failure import Failure
from typing import Union
from torba.constants import COIN from torba.constants import COIN
@ -42,7 +43,7 @@ from lbrynet.dht.error import TimeoutError
from lbrynet.core.Peer import Peer from lbrynet.core.Peer import Peer
from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader from lbrynet.core.SinglePeerDownloader import SinglePeerDownloader
from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader from lbrynet.core.client.StandaloneBlobDownloader import StandaloneBlobDownloader
from lbrynet.wallet.account import Account as LBRYAccount from lbrynet.wallet.account import Account as LBCAccount
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
requires = AuthJSONRPCServer.requires requires = AuthJSONRPCServer.requires
@ -805,7 +806,6 @@ class Daemon(AuthJSONRPCServer):
log.info("Get version info: " + json.dumps(platform_info)) log.info("Get version info: " + json.dumps(platform_info))
return self._render_response(platform_info) return self._render_response(platform_info)
# @AuthJSONRPCServer.deprecated() # deprecated actually disables the call
def jsonrpc_report_bug(self, message=None): def jsonrpc_report_bug(self, message=None):
""" """
Report a bug to slack Report a bug to slack
@ -2369,36 +2369,6 @@ class Daemon(AuthJSONRPCServer):
d.addCallback(lambda address: self._render_response(address)) d.addCallback(lambda address: self._render_response(address))
return d return d
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@AuthJSONRPCServer.deprecated("wallet_send")
@defer.inlineCallbacks
def jsonrpc_send_amount_to_address(self, amount, address):
"""
Queue a payment of credits to an address
Usage:
send_amount_to_address (<amount> | --amount=<amount>) (<address> | --address=<address>)
Options:
--amount=<amount> : (float) amount to send
--address=<address> : (str) address to send credits to
Returns:
(bool) true if payment successfully scheduled
"""
if amount < 0:
raise NegativeFundsError()
elif not amount:
raise NullFundsError()
reserved_points = self.wallet.reserve_points(address, amount)
if reserved_points is None:
raise InsufficientFundsError()
yield self.wallet.send_points_to_address(reserved_points, amount)
self.analytics_manager.send_credits_sent()
defer.returnValue(True)
@requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED]) @requires(WALLET_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks @defer.inlineCallbacks
def jsonrpc_wallet_send(self, amount, address=None, claim_id=None): def jsonrpc_wallet_send(self, amount, address=None, claim_id=None):
@ -2978,27 +2948,6 @@ class Daemon(AuthJSONRPCServer):
return self._blob_availability(blob_hash, search_timeout, blob_timeout) return self._blob_availability(blob_hash, search_timeout, blob_timeout)
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@AuthJSONRPCServer.deprecated("stream_availability")
def jsonrpc_get_availability(self, uri, sd_timeout=None, peer_timeout=None):
"""
Get stream availability for lbry uri
Usage:
get_availability (<uri> | --uri=<uri>) [<sd_timeout> | --sd_timeout=<sd_timeout>]
[<peer_timeout> | --peer_timeout=<peer_timeout>]
Options:
--uri=<uri> : (str) check availability for this uri
--sd_timeout=<sd_timeout> : (int) sd blob download timeout
--peer_timeout=<peer_timeout> : (int) how long to look for peers
Returns:
(float) Peers per blob / total blobs
"""
return self.jsonrpc_stream_availability(uri, peer_timeout, sd_timeout)
@requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED]) @requires(UPNP_COMPONENT, WALLET_COMPONENT, DHT_COMPONENT, conditions=[WALLET_IS_UNLOCKED])
@defer.inlineCallbacks @defer.inlineCallbacks
def jsonrpc_stream_availability(self, uri, search_timeout=None, blob_timeout=None): def jsonrpc_stream_availability(self, uri, search_timeout=None, blob_timeout=None):
@ -3102,39 +3051,22 @@ class Daemon(AuthJSONRPCServer):
response['head_blob_availability'].get('is_available') response['head_blob_availability'].get('is_available')
defer.returnValue(response) defer.returnValue(response)
@defer.inlineCallbacks #######################
def jsonrpc_cli_test_command(self, pos_arg, pos_args=[], pos_arg2=None, pos_arg3=None, # New Wallet Commands #
a_arg=False, b_arg=False): #######################
""" # TODO:
This command is only for testing the CLI argument parsing # Delete this after all commands have been migrated
Usage: # and refactored.
cli_test_command [--a_arg] [--b_arg] (<pos_arg> | --pos_arg=<pos_arg>)
[<pos_args>...] [--pos_arg2=<pos_arg2>]
[--pos_arg3=<pos_arg3>]
Options:
--a_arg : (bool) a arg
--b_arg : (bool) b arg
--pos_arg=<pos_arg> : (int) pos arg
--pos_args=<pos_args> : (int) pos args
--pos_arg2=<pos_arg2> : (int) pos arg 2
--pos_arg3=<pos_arg3> : (int) pos arg 3
Returns:
pos args
"""
out = (pos_arg, pos_args, pos_arg2, pos_arg3, a_arg, b_arg)
response = yield self._render_response(out)
defer.returnValue(response)
@requires("wallet") @requires("wallet")
def jsonrpc_account_balance(self, account_name=None, confirmations=6, def jsonrpc_balance(self, account_name=None, confirmations=6, include_reserved=False,
include_reserved=False, include_claims=False): include_claims=False):
""" """
Return the balance of an individual account or all of the accounts. Return the balance of an individual account or all of the accounts.
Usage: Usage:
account_balance [<account_name>] [--confirmations=<confirmations>] balance [<account_name>] [--confirmations=<confirmations>]
[--include_reserved] [--include_claims] [--include_reserved] [--include_claims]
Options: Options:
--account=<account_name> : (str) If provided only the balance for this --account=<account_name> : (str) If provided only the balance for this
@ -3150,7 +3082,7 @@ class Daemon(AuthJSONRPCServer):
if account_name: if account_name:
for account in self.wallet.accounts: for account in self.wallet.accounts:
if account.name == account_name: if account.name == account_name:
if include_claims and not isinstance(account, LBRYAccount): if include_claims and not isinstance(account, LBCAccount):
raise Exception( raise Exception(
"'--include-claims' requires specifying an LBC ledger account. " "'--include-claims' requires specifying an LBC ledger account. "
"Found '{}', but it's an {} ledger account." "Found '{}', but it's an {} ledger account."
@ -3170,7 +3102,7 @@ class Daemon(AuthJSONRPCServer):
return self.wallet.get_balances(confirmations) return self.wallet.get_balances(confirmations)
@requires("wallet") @requires("wallet")
def jsonrpc_account_max_gap(self, account_name): def jsonrpc_max_address_gap(self, account_name):
""" """
Finds ranges of consecutive addresses that are unused and returns the length Finds ranges of consecutive addresses that are unused and returns the length
of the longest such range: for change and receiving address chains. This is of the longest such range: for change and receiving address chains. This is
@ -3178,7 +3110,7 @@ class Daemon(AuthJSONRPCServer):
account settings. account settings.
Usage: Usage:
account_max_gap <account_name> max_address_gap <account_name>
Options: Options:
--account=<account_name> : (str) account for which to get max gaps --account=<account_name> : (str) account for which to get max gaps
@ -3186,10 +3118,89 @@ class Daemon(AuthJSONRPCServer):
Returns: Returns:
(map) maximum gap for change and receiving addresses (map) maximum gap for change and receiving addresses
""" """
return self.get_account_or_error('account', account_name).get_max_gap()
@requires("wallet")
def jsonrpc_fund(self, to_account, from_account, amount=0,
everything=False, outputs=1, broadcast=False):
"""
Transfer some amount (or --everything) to an account from another
account (can be the same account). Decimal amounts are interpreted
as LBC and non-decimal amounts are interpreted as dewies. You can
also spread the transfer across a number of --outputs (cannot be
used together with --everything).
Usage:
transfer (<to_account> | --to_account=<to_account>)
(<from_account> | --from_account=<from_account>)
(<amount> | --amount=<amount> | --everything)
[<outputs> | --outputs=<outputs>]
[--broadcast]
Options:
--to_account=<to_account> : (str) send to this account
--from_account=<from_account> : (str) spend from this account
--amount=<amount> : (str) the amount to transfer (lbc or dewies)
--everything : (bool) transfer everything (excluding claims), default: false.
--outputs=<outputs> : (int) split payment across many outputs, default: 1.
--broadcast : (bool) actually broadcast the transaction, default: false.
Returns:
(map) maximum gap for change and receiving addresses
"""
to_account = self.get_account_or_error('to_account', to_account)
from_account = self.get_account_or_error('from_account', from_account)
amount = self.get_dewies_or_error('amount', amount) if amount else None
if not isinstance(outputs, int):
raise ValueError("--outputs must be an integer.")
if everything and outputs > 1:
raise ValueError("Using --everything along with --outputs is not supported.")
return from_account.fund(
to_account=to_account, amount=amount, everything=everything,
outputs=outputs, broadcast=broadcast
).addCallback(lambda tx: self.tx_to_json(tx, from_account.ledger))
@staticmethod
def tx_to_json(tx, ledger):
return {
'txid': tx.id,
'inputs': [
{'amount': txi.amount, 'address': txi.txo_ref.txo.get_address(ledger)}
for txi in tx.inputs
],
'outputs': [
{'amount': txo.amount, 'address': txo.get_address(ledger)}
for txo in tx.outputs
],
'total_input': tx.input_sum,
'total_output': tx.input_sum,
'total_fee': tx.fee,
'xhex': hexlify(tx.raw).decode(),
}
def get_account_or_error(self, argument: str, account_name: str, lbc_only=False):
for account in self.wallet.accounts: for account in self.wallet.accounts:
if account.name == account_name: if account.name == account_name:
return account.get_max_gap() if lbc_only and not isinstance(account, LBCAccount):
raise Exception("Couldn't find an account named: '{}'.".format(account_name)) raise ValueError(
"Found '{}', but it's an {} ledger account. "
"'{}' requires specifying an LBC ledger account."
.format(account_name, account.ledger.symbol, argument)
)
return account
raise ValueError("Couldn't find an account named: '{}'.".format(account_name))
@staticmethod
def get_dewies_or_error(argument: str, amount: Union[str, int]):
if isinstance(amount, str):
if '.' in amount:
return int(Decimal(amount) * COIN)
elif amount.isdigit():
return int(amount)
elif isinstance(amount, int):
return amount
raise ValueError("Invalid value for '{}' argument: {}".format(argument, amount))
def loggly_time_string(dt): def loggly_time_string(dt):

View file

@ -101,6 +101,12 @@ class Account(BaseAccount):
}) })
defer.returnValue(channels) defer.returnValue(channels)
@classmethod
def get_private_key_from_seed(cls, ledger: 'baseledger.BaseLedger', seed: str, password: str):
return super().get_private_key_from_seed(
ledger, seed, password or 'lbryum'
)
@classmethod @classmethod
def from_dict(cls, ledger, d: dict) -> 'Account': def from_dict(cls, ledger, d: dict) -> 'Account':
account = super().from_dict(ledger, d) account = super().from_dict(ledger, d)

View file

@ -1,5 +1,6 @@
import os import os
import json import json
import logging
from twisted.internet import defer from twisted.internet import defer
from torba.basemanager import BaseWalletManager from torba.basemanager import BaseWalletManager
@ -13,6 +14,8 @@ from .account import generate_certificate
from .transaction import Transaction from .transaction import Transaction
from .database import WalletDatabase from .database import WalletDatabase
log = logging.getLogger(__name__)
class BackwardsCompatibleNetwork: class BackwardsCompatibleNetwork:
def __init__(self, manager): def __init__(self, manager):
@ -107,10 +110,16 @@ class LbryWalletManager(BaseWalletManager):
with open(wallet_file_path, 'w') as f: with open(wallet_file_path, 'w') as f:
f.write(json_data) f.write(json_data)
return cls.from_config({ manager = cls.from_config({
'ledgers': {ledger_id: ledger_config}, 'ledgers': {ledger_id: ledger_config},
'wallets': [wallet_file_path] 'wallets': [wallet_file_path]
}) })
if manager.default_account is None:
ledger = manager.get_or_create_ledger('lbc_mainnet')
log.info('Wallet at %s is empty, generating a default account.', wallet_file_path)
manager.default_wallet.generate_account(ledger)
manager.default_wallet.save()
return manager
def get_best_blockhash(self): def get_best_blockhash(self):
return defer.succeed('') return defer.succeed('')

View file

@ -11,7 +11,7 @@ class CLITest(unittest.TestCase):
def test_guess_type(self): def test_guess_type(self):
self.assertEqual('0.3.8', normalize_value('0.3.8')) self.assertEqual('0.3.8', normalize_value('0.3.8'))
self.assertEqual(0.3, normalize_value('0.3')) self.assertEqual('0.3', normalize_value('0.3'))
self.assertEqual(3, normalize_value('3')) self.assertEqual(3, normalize_value('3'))
self.assertEqual(3, normalize_value(3)) self.assertEqual(3, normalize_value(3))