lbry-sdk/lbrynet/wallet/manager.py

154 lines
5.5 KiB
Python
Raw Normal View History

import os
import logging
2018-03-27 08:40:44 +02:00
from operator import itemgetter
from twisted.internet import defer
import lbryschema
from .protocol import Network
2018-03-27 08:40:44 +02:00
from .blockchain import BlockchainHeaders, Transaction
from .wallet import Wallet
2018-03-27 08:40:44 +02:00
from .stream import execute_serially
log = logging.getLogger(__name__)
class WalletManager:
def __init__(self, storage, config):
self.storage = storage
self.config = config
lbryschema.BLOCKCHAIN_NAME = config['chain']
self.headers = BlockchainHeaders(self.headers_path, config['chain'])
2018-03-27 08:40:44 +02:00
self.wallet = Wallet(self.wallet_path, self.headers)
self.network = Network(config)
self.network.on_header.listen(self.process_header)
2018-03-27 08:40:44 +02:00
self.network.on_status.listen(self.process_status)
@property
def headers_path(self):
filename = 'blockchain_headers'
if self.config['chain'] != 'lbrycrd_main':
filename = '{}_headers'.format(self.config['chain'].split("_")[1])
return os.path.join(self.config['wallet_path'], filename)
@property
def wallet_path(self):
return os.path.join(self.config['wallet_path'], 'wallets', 'default_wallet')
2018-03-27 08:40:44 +02:00
def get_least_used_receiving_address(self, max_transactions=1000):
return self._get_least_used_address(
self.wallet.receiving_addresses,
self.wallet.default_account.receiving,
max_transactions
)
def get_least_used_change_address(self, max_transactions=100):
return self._get_least_used_address(
self.wallet.change_addresses,
self.wallet.default_account.change,
max_transactions
)
def _get_least_used_address(self, addresses, sequence, max_transactions):
transaction_counts = []
for address in addresses:
transactions = self.wallet.history.get_transactions(address, [])
tx_count = len(transactions)
if tx_count == 0:
return address
elif tx_count >= max_transactions:
continue
else:
transaction_counts.append((address, tx_count))
if transaction_counts:
transaction_counts.sort(key=itemgetter(1))
return transaction_counts[0]
address = sequence.generate_next_address()
self.subscribe_history(address)
return address
@defer.inlineCallbacks
def start(self):
self.network.start()
yield self.network.on_connected.first
2018-03-27 08:40:44 +02:00
yield self.update_headers()
yield self.network.subscribe_headers()
yield self.update_wallet()
def stop(self):
return self.network.stop()
2018-03-27 08:40:44 +02:00
@execute_serially
@defer.inlineCallbacks
2018-03-27 08:40:44 +02:00
def update_headers(self):
while True:
2018-03-27 08:40:44 +02:00
height_sought = len(self.headers)
headers = yield self.network.get_headers(height_sought)
log.info("received {} headers starting at {} height".format(headers['count'], height_sought))
if headers['count'] <= 0:
break
2018-03-27 08:40:44 +02:00
yield self.headers.connect(height_sought, headers['hex'].decode('hex'))
@defer.inlineCallbacks
2018-03-27 08:40:44 +02:00
def process_header(self, response):
header = response[0]
if self.update_headers.is_running:
return
2018-03-27 08:40:44 +02:00
if header['height'] == len(self.headers):
# New header from network directly connects after the last local header.
yield self.headers.connect(len(self.headers), header['hex'].decode('hex'))
2018-03-27 08:40:44 +02:00
elif header['height'] > len(self.headers):
# New header is several heights ahead of local, do download instead.
2018-03-27 08:40:44 +02:00
yield self.update_headers()
2018-03-27 08:40:44 +02:00
@execute_serially
@defer.inlineCallbacks
2018-03-27 08:40:44 +02:00
def update_wallet(self):
if not self.wallet.exists:
self.wallet.create()
# Before subscribing, download history for any addresses that don't have any,
# this avoids situation where we're getting status updates to addresses we know
# need to update anyways. Continue to get history and create more addresses until
# all missing addresses are created and history for them is fully restored.
self.wallet.ensure_enough_addresses()
addresses = list(self.wallet.addresses_without_history)
while addresses:
yield defer.gatherResults([
self.update_history(a) for a in addresses
])
2018-03-27 08:40:44 +02:00
addresses = self.wallet.ensure_enough_addresses()
# By this point all of the addresses should be restored and we
# can now subscribe all of them to receive updates.
yield defer.gatherResults([
self.subscribe_history(address)
for address in self.wallet.addresses
])
2018-03-27 08:40:44 +02:00
@defer.inlineCallbacks
def update_history(self, address):
history = yield self.network.get_history(address)
for hash in map(itemgetter('tx_hash'), history):
transaction = self.wallet.history.get_transaction(hash)
if not transaction:
raw = yield self.network.get_transaction(hash)
transaction = Transaction(hash, raw, None)
self.wallet.history.add_transaction(address, transaction)
@defer.inlineCallbacks
def subscribe_history(self, address):
status = yield self.network.subscribe_address(address)
if status != self.wallet.history.get_status(address):
self.update_history(address)
def process_status(self, response):
address, status = response
if status != self.wallet.history.get_status(address):
self.update_history(address)