# standard imports import logging import uuid # third-party imports from chainlib.chain import ChainSpec # local imports from chainsyncer.db.models.sync import BlockchainSync from chainsyncer.db.models.filter import BlockchainSyncFilter from chainsyncer.db.models.base import SessionBase logg = logging.getLogger() class SyncerBackend: """Interface to block and transaction sync state. :param chain_spec: Chain spec for the chain that syncer is running for. :type chain_spec: cic_registry.chain.ChainSpec :param object_id: Unique id for the syncer session. :type object_id: number """ def __init__(self, chain_spec, object_id): self.db_session = None self.db_object = None self.db_object_filter = None self.chain_spec = chain_spec self.object_id = object_id self.connect() self.disconnect() def connect(self): """Loads the state of the syncer session with the given id. """ if self.db_session == None: self.db_session = SessionBase.create_session() q = self.db_session.query(BlockchainSync) q = q.filter(BlockchainSync.id==self.object_id) self.db_object = q.first() if self.db_object != None: qtwo = self.db_session.query(BlockchainSyncFilter) qtwo = qtwo.join(BlockchainSync) qtwo = qtwo.filter(BlockchainSync.id==self.db_object.id) self.db_object_filter = qtwo.first() if self.db_object == None: raise ValueError('sync entry with id {} not found'.format(self.object_id)) def disconnect(self): """Commits state of sync to backend. """ if self.db_object_filter != None: self.db_session.add(self.db_object_filter) self.db_session.add(self.db_object) self.db_session.commit() self.db_session.close() self.db_session = None def chain(self): """Returns chain spec for syncer :returns: Chain spec :rtype chain_spec: cic_registry.chain.ChainSpec """ return self.chain_spec def get(self): """Get the current state of the syncer cursor. :returns: Block and block transaction height, respectively :rtype: tuple """ self.connect() pair = self.db_object.cursor() filter_state = self.db_object_filter.filter() self.disconnect() return (pair, filter_state,) def set(self, block_height, tx_height): """Update the state of the syncer cursor :param block_height: Block height of cursor :type block_height: number :param tx_height: Block transaction height of cursor :type tx_height: number :returns: Block and block transaction height, respectively :rtype: tuple """ self.connect() pair = self.db_object.set(block_height, tx_height) filter_state = self.db_object_filter.filter() self.disconnect() return (pair, filter_state,) def start(self): """Get the initial state of the syncer cursor. :returns: Initial block and block transaction height, respectively :rtype: tuple """ self.connect() pair = self.db_object.start() filter_state = self.db_object_filter.start() self.disconnect() return (pair, filter_state,) def target(self): """Get the target state (upper bound of sync) of the syncer cursor. :returns: Target block height :rtype: number """ self.connect() target = self.db_object.target() filter_state = self.db_object_filter.target() self.disconnect() return (target, filter_target,) @staticmethod def first(chain_spec): """Returns the model object of the most recent syncer in backend. :param chain: Chain spec of chain that syncer is running for. :type chain: cic_registry.chain.ChainSpec :returns: Last syncer object :rtype: cic_eth.db.models.BlockchainSync """ #return BlockchainSync.first(str(chain_spec)) object_id = BlockchainSync.first(str(chain_spec)) if object_id == None: return None return SyncerBackend(chain_spec, object_id) @staticmethod def initial(chain, block_height): """Creates a new syncer session and commit its initial state to backend. :param chain: Chain spec of chain that syncer is running for. :type chain: cic_registry.chain.ChainSpec :param block_height: Target block height :type block_height: number :returns: New syncer object :rtype: cic_eth.db.models.BlockchainSync """ object_id = None session = SessionBase.create_session() o = BlockchainSync(chain, 0, 0, block_height) session.add(o) session.commit() object_id = o.id session.close() return SyncerBackend(chain, object_id) @staticmethod def resume(chain, block_height): """Retrieves and returns all previously unfinished syncer sessions. :param chain: Chain spec of chain that syncer is running for. :type chain: cic_registry.chain.ChainSpec :param block_height: Target block height :type block_height: number :returns: Syncer objects of unfinished syncs :rtype: list of cic_eth.db.models.BlockchainSync """ syncers = [] session = SessionBase.create_session() object_id = None for object_id in BlockchainSync.get_unsynced(session=session): logg.debug('block syncer resume added previously unsynced sync entry id {}'.format(object_id)) syncers.append(SyncerBackend(chain, object_id)) (block_resume, tx_resume) = BlockchainSync.get_last_live_height(block_height, session=session) if block_height != block_resume: o = BlockchainSync(chain, block_resume, tx_resume, block_height) session.add(o) session.commit() object_id = o.id syncers.append(SyncerBackend(chain, object_id)) logg.debug('block syncer resume added new sync entry from previous run id {}, start{}:{} target {}'.format(object_id, block_resume, tx_resume, block_height)) session.close() return syncers @staticmethod def live(chain_spec, block_height): """Creates a new open-ended syncer session starting at the given block height. :param chain: Chain spec of chain that syncer is running for. :type chain: cic_registry.chain.ChainSpec :param block_height: Target block height :type block_height: number :returns: "Live" syncer object :rtype: cic_eth.db.models.BlockchainSync """ object_id = None session = SessionBase.create_session() o = BlockchainSync(str(chain_spec), block_height, 0, None) session.add(o) session.flush() object_id = o.id of = BlockchainSyncFilter(o) session.add(of) session.commit() session.close() return SyncerBackend(chain_spec, object_id) def register_filter(self, name): self.connect() if self.db_object_filter == None: self.db_object_filter = BlockchainSyncFilter(self.db_object) self.db_object_filter.add(name) self.db_session.add(self.db_object_filter) self.disconnect() class MemBackend: def __init__(self, chain_spec, object_id): self.object_id = object_id self.chain_spec = chain_spec self.block_height = 0 self.tx_height = 0 self.flags = 0 self.db_session = None def connect(self): pass def disconnect(self): pass def set(self, block_height, tx_height): logg.debug('stateless backend received {} {}'.format(block_height, tx_height)) self.block_height = block_height self.tx_height = tx_height def get(self): return (self.block_height, self.tx_height)