+ get_detailed_accounts

This commit is contained in:
Lex Berezhny 2018-08-30 11:50:11 -04:00
parent 00cf8131ec
commit 9e55694512
4 changed files with 75 additions and 42 deletions

View file

@ -5,6 +5,7 @@ from twisted.internet import defer
from torba.mnemonic import Mnemonic from torba.mnemonic import Mnemonic
from torba.bip32 import PrivateKey, PubKey, from_extended_key_string from torba.bip32 import PrivateKey, PubKey, from_extended_key_string
from torba.hash import double_sha256, aes_encrypt, aes_decrypt from torba.hash import double_sha256, aes_encrypt, aes_decrypt
from torba.constants import COIN
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from torba import baseledger from torba import baseledger
@ -65,15 +66,15 @@ class AddressManager:
@defer.inlineCallbacks @defer.inlineCallbacks
def get_addresses(self, limit: int = None, only_usable: bool = False) -> defer.Deferred: def get_addresses(self, limit: int = None, only_usable: bool = False) -> defer.Deferred:
records = yield self.get_address_records(limit=limit, only_usable=only_usable) records = yield self.get_address_records(limit=limit, only_usable=only_usable)
defer.returnValue([r['address'] for r in records]) return [r['address'] for r in records]
@defer.inlineCallbacks @defer.inlineCallbacks
def get_or_create_usable_address(self) -> defer.Deferred: def get_or_create_usable_address(self) -> defer.Deferred:
addresses = yield self.get_addresses(limit=1, only_usable=True) addresses = yield self.get_addresses(limit=1, only_usable=True)
if addresses: if addresses:
defer.returnValue(addresses[0]) return addresses[0]
addresses = yield self.ensure_address_gap() addresses = yield self.ensure_address_gap()
defer.returnValue(addresses[0]) return addresses[0]
class HierarchicalDeterministic(AddressManager): class HierarchicalDeterministic(AddressManager):
@ -109,7 +110,7 @@ class HierarchicalDeterministic(AddressManager):
yield self.db.add_keys( yield self.db.add_keys(
self.account, self.chain_number, new_keys self.account, self.chain_number, new_keys
) )
defer.returnValue([key[1].address for key in new_keys]) return [key[1].address for key in new_keys]
@defer.inlineCallbacks @defer.inlineCallbacks
def get_max_gap(self) -> defer.Deferred: def get_max_gap(self) -> defer.Deferred:
@ -122,7 +123,7 @@ class HierarchicalDeterministic(AddressManager):
else: else:
max_gap = max(max_gap, current_gap) max_gap = max(max_gap, current_gap)
current_gap = 0 current_gap = 0
defer.returnValue(max_gap) return max_gap
@defer.inlineCallbacks @defer.inlineCallbacks
def ensure_address_gap(self) -> defer.Deferred: def ensure_address_gap(self) -> defer.Deferred:
@ -136,12 +137,12 @@ class HierarchicalDeterministic(AddressManager):
break break
if existing_gap == self.gap: if existing_gap == self.gap:
defer.returnValue([]) return []
start = addresses[0]['position']+1 if addresses else 0 start = addresses[0]['position']+1 if addresses else 0
end = start + (self.gap - existing_gap) end = start + (self.gap - existing_gap)
new_keys = yield self.generate_keys(start, end-1) new_keys = yield self.generate_keys(start, end-1)
defer.returnValue(new_keys) return new_keys
def get_address_records(self, limit: int = None, only_usable: bool = False): def get_address_records(self, limit: int = None, only_usable: bool = False):
return self._query_addresses( return self._query_addresses(
@ -179,8 +180,8 @@ class SingleKey(AddressManager):
yield self.db.add_keys( yield self.db.add_keys(
self.account, self.chain_number, [(0, self.public_key)] self.account, self.chain_number, [(0, self.public_key)]
) )
defer.returnValue([self.public_key.address]) return [self.public_key.address]
defer.returnValue([]) return []
def get_address_records(self, limit: int = None, only_usable: bool = False) -> defer.Deferred: def get_address_records(self, limit: int = None, only_usable: bool = False) -> defer.Deferred:
return self._query_addresses() return self._query_addresses()
@ -201,6 +202,7 @@ class BaseAccount:
address_generator: dict) -> None: address_generator: dict) -> None:
self.ledger = ledger self.ledger = ledger
self.wallet = wallet self.wallet = wallet
self.id = public_key.address
self.name = name self.name = name
self.seed = seed self.seed = seed
self.encrypted = encrypted self.encrypted = encrypted
@ -271,6 +273,22 @@ class BaseAccount:
'address_generator': self.address_generator.to_dict(self.receiving, self.change) 'address_generator': self.address_generator.to_dict(self.receiving, self.change)
} }
@defer.inlineCallbacks
def get_details(self, show_seed=False, **kwargs):
satoshis = yield self.get_balance(**kwargs)
details = {
'id': self.id,
'name': self.name,
'coins': round(satoshis/COIN, 2),
'satoshis': satoshis,
'encrypted': self.encrypted,
'public_key': self.public_key.extended_key_string(),
'address_generator': self.address_generator.to_dict(self.receiving, self.change)
}
if show_seed:
details['seed'] = self.seed
return details
def decrypt(self, password): def decrypt(self, password):
assert self.encrypted, "Key is not encrypted." assert self.encrypted, "Key is not encrypted."
secret = double_sha256(password) secret = double_sha256(password)
@ -291,12 +309,12 @@ class BaseAccount:
for address_manager in self.address_managers: for address_manager in self.address_managers:
new_addresses = yield address_manager.ensure_address_gap() new_addresses = yield address_manager.ensure_address_gap()
addresses.extend(new_addresses) addresses.extend(new_addresses)
defer.returnValue(addresses) return addresses
@defer.inlineCallbacks @defer.inlineCallbacks
def get_addresses(self, limit: int = None, max_used_times: int = None) -> defer.Deferred: def get_addresses(self, limit: int = None, max_used_times: int = None) -> defer.Deferred:
records = yield self.get_address_records(limit, max_used_times) records = yield self.get_address_records(limit, max_used_times)
defer.returnValue([r['address'] for r in records]) return [r['address'] for r in records]
def get_address_records(self, limit: int = None, max_used_times: int = None) -> defer.Deferred: def get_address_records(self, limit: int = None, max_used_times: int = None) -> defer.Deferred:
return self.ledger.db.get_addresses(self, None, limit, max_used_times) return self.ledger.db.get_addresses(self, None, limit, max_used_times)
@ -316,14 +334,17 @@ class BaseAccount:
def get_max_gap(self): def get_max_gap(self):
change_gap = yield self.change.get_max_gap() change_gap = yield self.change.get_max_gap()
receiving_gap = yield self.receiving.get_max_gap() receiving_gap = yield self.receiving.get_max_gap()
defer.returnValue({ return {
'max_change_gap': change_gap, 'max_change_gap': change_gap,
'max_receiving_gap': receiving_gap, 'max_receiving_gap': receiving_gap,
}) }
def get_unspent_outputs(self, **constraints): def get_unspent_outputs(self, **constraints):
return self.ledger.db.get_utxos_for_account(self, **constraints) return self.ledger.db.get_utxos_for_account(self, **constraints)
def get_inputs_outputs(self, **constraints):
return self.ledger.db.get_txios_for_account(self, **constraints)
@defer.inlineCallbacks @defer.inlineCallbacks
def fund(self, to_account, amount=None, everything=False, def fund(self, to_account, amount=None, everything=False,
outputs=1, broadcast=False, **constraints): outputs=1, broadcast=False, **constraints):
@ -360,4 +381,4 @@ class BaseAccount:
[txi.txo_ref.txo for txi in tx.inputs] [txi.txo_ref.txo for txi in tx.inputs]
) )
defer.returnValue(tx) return tx

View file

@ -86,25 +86,25 @@ class SQLiteMixin:
def query_one_value(self, query, params=None, default=None): def query_one_value(self, query, params=None, default=None):
result = yield self.run_query(query, params) result = yield self.run_query(query, params)
if result: if result:
defer.returnValue(result[0][0] or default) return result[0][0] or default
else: else:
defer.returnValue(default) return default
@defer.inlineCallbacks @defer.inlineCallbacks
def query_dict_value_list(self, query, fields, params=None): def query_dict_value_list(self, query, fields, params=None):
result = yield self.run_query(query.format(', '.join(fields)), params) result = yield self.run_query(query.format(', '.join(fields)), params)
if result: if result:
defer.returnValue([dict(zip(fields, r)) for r in result]) return [dict(zip(fields, r)) for r in result]
else: else:
defer.returnValue([]) return []
@defer.inlineCallbacks @defer.inlineCallbacks
def query_dict_value(self, query, fields, params=None, default=None): def query_dict_value(self, query, fields, params=None, default=None):
result = yield self.query_dict_value_list(query, fields, params) result = yield self.query_dict_value_list(query, fields, params)
if result: if result:
defer.returnValue(result[0]) return result[0]
else: else:
defer.returnValue(default) return default
@staticmethod @staticmethod
def execute(t, sql, values): def execute(t, sql, values):
@ -262,9 +262,9 @@ class BaseDatabase(SQLiteMixin):
"SELECT raw, height, is_verified FROM tx WHERE txid = ?", (txid,) "SELECT raw, height, is_verified FROM tx WHERE txid = ?", (txid,)
) )
if result: if result:
defer.returnValue(result[0]) return result[0]
else: else:
defer.returnValue((None, None, False)) return None, None, False
def get_balance_for_account(self, account, include_reserved=False, **constraints): def get_balance_for_account(self, account, include_reserved=False, **constraints):
if not include_reserved: if not include_reserved:
@ -294,14 +294,34 @@ class BaseDatabase(SQLiteMixin):
"""+constraints_to_sql(constraints), constraints """+constraints_to_sql(constraints), constraints
) )
output_class = account.ledger.transaction_class.output_class output_class = account.ledger.transaction_class.output_class
defer.returnValue([ return [
output_class( output_class(
values[0], values[0],
output_class.script_class(values[1]), output_class.script_class(values[1]),
TXRefImmutable.from_id(values[2]), TXRefImmutable.from_id(values[2]),
position=values[3] position=values[3]
) for values in utxos ) for values in utxos
]) ]
@defer.inlineCallbacks
def get_txios_for_account(self, account, **constraints):
constraints['account'] = account.public_key.address
utxos = yield self.run_query(
"""
SELECT amount, script, txid, txo.position
FROM txo JOIN pubkey_address ON pubkey_address.address=txo.address
WHERE account=:account AND txo.is_reserved=0 AND txoid NOT IN (SELECT txoid FROM txi)
"""+constraints_to_sql(constraints), constraints
)
output_class = account.ledger.transaction_class.output_class
return [
output_class(
values[0],
output_class.script_class(values[1]),
TXRefImmutable.from_id(values[2]),
position=values[3]
) for values in utxos
]
def add_keys(self, account, chain, keys): def add_keys(self, account, chain, keys):
sql = ( sql = (

View file

@ -129,11 +129,8 @@ class BaseLedger(metaclass=LedgerRegistry):
def path(self): def path(self):
return os.path.join(self.config['data_path'], self.get_id()) return os.path.join(self.config['data_path'], self.get_id())
@defer.inlineCallbacks def add_account(self, account: baseaccount.BaseAccount):
def add_account(self, account: baseaccount.BaseAccount) -> defer.Deferred:
self.accounts.append(account) self.accounts.append(account)
if self.network.is_connected:
yield self.update_account(account)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_transaction(self, txhash): def get_transaction(self, txhash):

View file

@ -43,20 +43,15 @@ class BaseWalletManager:
return wallet return wallet
@defer.inlineCallbacks @defer.inlineCallbacks
def get_balances(self, confirmations=6): def get_detailed_accounts(self, confirmations=6, show_seed=False):
balances = {} ledgers = {}
for i, ledger in enumerate(self.ledgers.values()): for i, account in enumerate(self.accounts):
ledger_balances = balances[ledger.get_id()] = [] details = yield account.get_details(confirmations=confirmations, show_seed=True)
for j, account in enumerate(ledger.accounts): details['is_default_account'] = i == 0
satoshis = yield account.get_balance(confirmations) ledger_id = account.ledger.get_id()
ledger_balances.append({ ledgers.setdefault(ledger_id, [])
'account': account.name, ledgers[ledger_id].append(details)
'coins': round(satoshis/COIN, 2), return ledgers
'satoshis': satoshis,
'is_default_account': i == j == 0,
'id': account.public_key.address
})
defer.returnValue(balances)
@property @property
def default_wallet(self): def default_wallet(self):