import os import asyncio from concurrent import futures from collections import namedtuple, deque import sqlite3 import apsw DDL = """ pragma journal_mode=WAL; create table if not exists block ( block_hash bytes not null primary key, previous_hash bytes not null, file_number integer not null, height int ); create table if not exists tx ( block_hash integer not null, position integer not null, tx_hash bytes not null ); create table if not exists txi ( block_hash bytes not null, tx_hash bytes not null, txo_hash bytes not null ); create table if not exists claim ( txo_hash bytes not null, claim_hash bytes not null, claim_name text not null, amount integer not null, height integer ); create table if not exists claim_history ( block_hash bytes not null, tx_hash bytes not null, tx_position integer not null, txo_hash bytes not null, claim_hash bytes not null, claim_name text not null, action integer not null, amount integer not null, height integer, is_spent bool ); create table if not exists support ( block_hash bytes not null, tx_hash bytes not null, txo_hash bytes not null, claim_hash bytes not null, amount integer not null ); """ class BlockchainDB: __slots__ = 'db', 'directory' def __init__(self, path: str): self.db = None self.directory = path @property def db_file_path(self): return os.path.join(self.directory, 'blockchain.db') def open(self): self.db = sqlite3.connect(self.db_file_path, isolation_level=None, uri=True, timeout=60.0 * 5) self.db.executescript(""" pragma journal_mode=wal; """) # self.db = apsw.Connection( # self.db_file_path, # flags=( # apsw.SQLITE_OPEN_READWRITE | # apsw.SQLITE_OPEN_CREATE | # apsw.SQLITE_OPEN_URI # ) # ) self.execute_ddl(DDL) self.execute(f"ATTACH ? AS block_index", ('file:'+os.path.join(self.directory, 'block_index.sqlite')+'?mode=ro',)) #def exec_factory(cursor, statement, bindings): # tpl = namedtuple('row', (d[0] for d in cursor.getdescription())) # cursor.setrowtrace(lambda cursor, row: tpl(*row)) # return True #self.db.setexectrace(exec_factory) def row_factory(cursor, row): tpl = namedtuple('row', (d[0] for d in cursor.description)) return tpl(*row) self.db.row_factory = row_factory return self def close(self): if self.db is not None: self.db.close() def execute(self, *args): return self.db.cursor().execute(*args) def execute_many(self, *args): return self.db.cursor().executemany(*args) def execute_many_tx(self, *args): cursor = self.db.cursor() cursor.execute('begin;') result = cursor.executemany(*args) cursor.execute('commit;') return result def execute_ddl(self, *args): self.db.executescript(*args) #deque(self.execute(*args), maxlen=0) def begin(self): self.execute('begin;') def commit(self): self.execute('commit;') def get_block_file_path_from_number(self, block_file_number): return os.path.join(self.directory, 'blocks', f'blk{block_file_number:05}.dat') def get_block_files_not_synced(self): return list(self.execute( """ SELECT file as file_number, COUNT(hash) as blocks, SUM(txcount) as txs FROM block_index.block_info WHERE hash NOT IN (SELECT block_hash FROM block) GROUP BY file ORDER BY file ASC; """ )) def get_blocks_not_synced(self, block_file): return self.execute( """ SELECT datapos as data_offset, height, hash as block_hash, txCount as txs FROM block_index.block_info WHERE file = ? AND hash NOT IN (SELECT block_hash FROM block) ORDER BY datapos ASC; """, (block_file,) ) class AsyncBlockchainDB: def __init__(self, db: BlockchainDB): self.sync_db = db self.executor = futures.ThreadPoolExecutor(max_workers=1) @classmethod def from_path(cls, path: str) -> 'AsyncBlockchainDB': return cls(BlockchainDB(path)) def get_block_file_path_from_number(self, block_file_number): return self.sync_db.get_block_file_path_from_number(block_file_number) async def run_in_executor(self, func, *args): return await asyncio.get_running_loop().run_in_executor( self.executor, func, *args ) async def open(self): return await self.run_in_executor(self.sync_db.open) async def close(self): return await self.run_in_executor(self.sync_db.close) async def get_block_files_not_synced(self): return await self.run_in_executor(self.sync_db.get_block_files_not_synced)