client wallet sync

This commit is contained in:
Lex Berezhny 2020-12-18 10:44:58 -05:00
parent becef285f6
commit d74fa05a8b
8 changed files with 99 additions and 39 deletions

View file

@ -232,6 +232,9 @@ class Database:
async def get_best_block_height(self) -> int: async def get_best_block_height(self) -> int:
return await self.run(q.get_best_block_height) return await self.run(q.get_best_block_height)
async def get_best_block_filter(self) -> int:
return await self.run(q.get_best_block_filter)
async def process_all_things_after_sync(self): async def process_all_things_after_sync(self):
return await self.run(sync.process_all_things_after_sync) return await self.run(sync.process_all_things_after_sync)
@ -244,6 +247,9 @@ class Database:
async def insert_block(self, block): async def insert_block(self, block):
return await self.run(q.insert_block, block) return await self.run(q.insert_block, block)
async def insert_block_filter(self, height: int, address_filter: bytes):
return await self.run(q.insert_block_filter, height, address_filter)
async def insert_transaction(self, block_hash, tx): async def insert_transaction(self, block_hash, tx):
return await self.run(q.insert_transaction, block_hash, tx) return await self.run(q.insert_transaction, block_hash, tx)

View file

@ -40,6 +40,10 @@ def get_best_block_height():
return context().fetchmax(Block.c.height, -1) return context().fetchmax(Block.c.height, -1)
def get_best_block_filter():
return context().fetchmax(BlockFilter.c.height, -1)
def insert_block(block): def insert_block(block):
context().get_bulk_loader().add_block(block).flush(return_row_count_for_table=None) context().get_bulk_loader().add_block(block).flush(return_row_count_for_table=None)
@ -56,6 +60,12 @@ def get_block_headers(first, last=None):
return context().fetchall(query) return context().fetchall(query)
def insert_block_filter(height: int, address_filter: bytes):
loader = context().get_bulk_loader()
loader.add_block_filter(height, address_filter)
loader.flush(return_row_count_for_table=None)
def get_filters(start_height, end_height=None, granularity=0): def get_filters(start_height, end_height=None, granularity=0):
assert granularity >= 0, "filter granularity must be 0 or positive number" assert granularity >= 0, "filter granularity must be 0 or positive number"
if granularity == 0: if granularity == 0:

View file

@ -72,7 +72,7 @@ class QueryContext:
if self._variable_limit is not None: if self._variable_limit is not None:
return self._variable_limit return self._variable_limit
if self.is_sqlite: if self.is_sqlite:
for result in self.fetchall('PRAGMA COMPILE_OPTIONS;'): for result in self.fetchall(text('PRAGMA COMPILE_OPTIONS;')):
for _, value in result.items(): for _, value in result.items():
if value.startswith('MAX_VARIABLE_NUMBER'): if value.startswith('MAX_VARIABLE_NUMBER'):
self._variable_limit = int(value.split('=')[1]) self._variable_limit = int(value.split('=')[1])

View file

@ -3458,6 +3458,10 @@ class Client(API):
raise ValueError(f'Unknown message received: {d}') raise ValueError(f'Unknown message received: {d}')
await controller.close() await controller.close()
del self.requests[d['id']] del self.requests[d['id']]
elif 'event' in d:
controller = self.subscriptions.get(d['event'])
if controller is not None:
await controller.add(d['payload'])
elif 'method' in d and d['method'].startswith('event'): elif 'method' in d and d['method'].startswith('event'):
print(d) print(d)
else: else:

View file

@ -76,12 +76,12 @@ class Service:
async def start(self): async def start(self):
await self.db.open() await self.db.open()
await self.wallets.storage.prepare() await self.wallets.open()
await self.wallets.initialize()
await self.sync.start() await self.sync.start()
async def stop(self): async def stop(self):
await self.sync.stop() await self.sync.stop()
await self.wallets.close()
await self.db.close() await self.db.close()
async def get_status(self): async def get_status(self):

View file

@ -6,6 +6,7 @@ from binascii import unhexlify
from lbry.blockchain.block import Block, get_address_filter from lbry.blockchain.block import Block, get_address_filter
from lbry.event import BroadcastSubscription from lbry.event import BroadcastSubscription
from lbry.crypto.hash import hash160
from lbry.wallet.account import AddressManager from lbry.wallet.account import AddressManager
from lbry.blockchain import Ledger, Transaction from lbry.blockchain import Ledger, Transaction
from lbry.db import Database from lbry.db import Database
@ -110,33 +111,15 @@ class FilterManager:
self.client = client self.client = client
self.cache = {} self.cache = {}
async def download(self): async def download(self, best_height):
filters = await self.client.first.address_filter(start_height=0, end_height=500, granularity=0) our_height = await self.db.get_best_block_filter()
#address = None new_block_filters = await self.client.address_filter(
#address_array = [bytearray(self.db.ledger.address_to_hash160(address))] start_height=our_height+1, end_height=best_height, granularity=1
#for address_filter in filters: )
# print(address_filter) for block_filter in await new_block_filters.first:
# address_filter = get_address_filter(unhexlify(address_filter['filter'])) await self.db.insert_block_filter(
# print(address_filter.MatchAny(address_array)) block_filter["height"], unhexlify(block_filter["filter"])
)
# address_array = [
# bytearray(a['address'].encode())
# for a in await self.service.db.get_all_addresses()
# ]
# block_filters = await self.service.get_block_address_filters()
# for block_hash, block_filter in block_filters.items():
# bf = get_address_filter(block_filter)
# if bf.MatchAny(address_array):
# print(f'match: {block_hash} - {block_filter}')
# tx_filters = await self.service.get_transaction_address_filters(block_hash=block_hash)
# for txid, tx_filter in tx_filters.items():
# tf = get_address_filter(tx_filter)
# if tf.MatchAny(address_array):
# print(f' match: {txid} - {tx_filter}')
# txs = await self.service.search_transactions([txid])
# tx = Transaction(unhexlify(txs[txid]))
# await self.service.db.insert_transaction(tx)
async def get_filters(self, start_height, end_height, granularity): async def get_filters(self, start_height, end_height, granularity):
return await self.client.address_filter( return await self.client.address_filter(
@ -156,9 +139,8 @@ class BlockHeaderManager:
self.client = client self.client = client
self.cache = {} self.cache = {}
async def download(self): async def download(self, best_height):
our_height = await self.db.get_best_block_height() our_height = await self.db.get_best_block_height()
best_height = await self.client.first.block_tip()
for block in await self.client.first.block_list(start_height=our_height+1, end_height=best_height): for block in await self.client.first.block_list(start_height=our_height+1, end_height=best_height):
await self.db.insert_block(Block( await self.db.insert_block(Block(
height=block["height"], height=block["height"],
@ -194,10 +176,10 @@ class FastSync(Sync):
self.filters = FilterManager(self.db, self.client) self.filters = FilterManager(self.db, self.client)
async def get_block_headers(self, start_height: int, end_height: int = None): async def get_block_headers(self, start_height: int, end_height: int = None):
return await self.client.block_list(start_height, end_height) return await self.client.first.block_list(start_height, end_height)
async def get_best_block_height(self) -> int: async def get_best_block_height(self) -> int:
return await self.client.block_tip() return await self.client.first.block_tip()
async def start(self): async def start(self):
self.advance_loop_task = asyncio.create_task(self.advance()) self.advance_loop_task = asyncio.create_task(self.advance())
@ -213,11 +195,56 @@ class FastSync(Sync):
task.cancel() task.cancel()
async def advance(self): async def advance(self):
best_height = await self.client.first.block_tip()
await asyncio.wait([ await asyncio.wait([
self.blocks.download(), self.blocks.download(best_height),
self.filters.download() self.filters.download(best_height),
]) ])
block_filters = {}
for block_filter in await self.db.get_filters(0, best_height, 1):
block_filters[block_filter['height']] = \
get_address_filter(unhexlify(block_filter['filter']))
for wallet in self.service.wallets:
for account in wallet.accounts:
for address_manager in account.address_managers.values():
i = gap = 0
while gap < 20:
key, i = address_manager.public_key.child(i), i+1
address = bytearray(hash160(key.pubkey_bytes))
for block, matcher in block_filters.items():
if matcher.Match(address):
gap = 0
continue
gap += 1
# address = None
# address_array = [bytearray(self.db.ledger.address_to_hash160(address))]
# for address_filter in filters:
# print(address_filter)
# address_filter = get_address_filter(unhexlify(address_filter['filter']))
# print(address_filter.MatchAny(address_array))
# address_array = [
# bytearray(a['address'].encode())
# for a in await self.service.db.get_all_addresses()
# ]
# block_filters = await self.service.get_block_address_filters()
# for block_hash, block_filter in block_filters.items():
# bf = get_address_filter(block_filter)
# if bf.MatchAny(address_array):
# print(f'match: {block_hash} - {block_filter}')
# tx_filters = await self.service.get_transaction_address_filters(block_hash=block_hash)
# for txid, tx_filter in tx_filters.items():
# tf = get_address_filter(tx_filter)
# if tf.MatchAny(address_array):
# print(f' match: {txid} - {tx_filter}')
# txs = await self.service.search_transactions([txid])
# tx = Transaction(unhexlify(txs[txid]))
# await self.service.db.insert_transaction(tx)
async def loop(self): async def loop(self):
while True: while True:
try: try:

View file

@ -465,8 +465,8 @@ class IntegrationTestCase(AsyncioTestCase):
service = FullNode(ledger) service = FullNode(ledger)
console = Console(service) console = Console(service)
daemon = Daemon(service, console) daemon = Daemon(service, console)
self.addCleanup(daemon.stop)
if start: if start:
self.addCleanup(daemon.stop)
await daemon.start() await daemon.start()
return daemon return daemon
@ -485,8 +485,8 @@ class IntegrationTestCase(AsyncioTestCase):
service = FullEndpoint(ledger) service = FullEndpoint(ledger)
console = Console(service) console = Console(service)
daemon = Daemon(service, console) daemon = Daemon(service, console)
self.addCleanup(daemon.stop)
if start: if start:
self.addCleanup(daemon.stop)
await daemon.start() await daemon.start()
return daemon return daemon
@ -504,8 +504,8 @@ class IntegrationTestCase(AsyncioTestCase):
service = LightClient(ledger) service = LightClient(ledger)
console = Console(service) console = Console(service)
daemon = Daemon(service, console) daemon = Daemon(service, console)
self.addCleanup(daemon.stop)
if start: if start:
self.addCleanup(daemon.stop)
await daemon.start() await daemon.start()
return daemon return daemon

View file

@ -27,6 +27,12 @@ class WalletManager:
else: else:
raise Exception(f"Unknown wallet storage format: {self.ledger.conf.wallet_storage}") raise Exception(f"Unknown wallet storage format: {self.ledger.conf.wallet_storage}")
def __len__(self):
return self.wallets.__len__()
def __iter__(self):
return self.wallets.values().__iter__()
def __getitem__(self, wallet_id: str) -> Wallet: def __getitem__(self, wallet_id: str) -> Wallet:
try: try:
return self.wallets[wallet_id] return self.wallets[wallet_id]
@ -56,6 +62,13 @@ class WalletManager:
raise ValueError("Cannot spend funds with locked wallet, unlock first.") raise ValueError("Cannot spend funds with locked wallet, unlock first.")
return wallet return wallet
async def open(self):
await self.storage.prepare()
await self.initialize()
async def close(self):
pass
async def initialize(self): async def initialize(self):
for wallet_id in self.ledger.conf.wallets: for wallet_id in self.ledger.conf.wallets:
if wallet_id in self.wallets: if wallet_id in self.wallets: