# standard imports import datetime # third-party imports from sqlalchemy import Column, String, Integer, DateTime, Text, Boolean from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method # local imports from cic_eth.db.models.base import SessionBase class BlockchainSync(SessionBase): """Syncer control backend. :param chain: Chain spec string representation :type chain: str :param block_start: Block number to start sync from :type block_start: number :param tx_start: Block transaction number to start sync from :type tx_start: number :param block_target: Block number to sync until, inclusive :type block_target: number """ __tablename__ = 'blockchain_sync' blockchain = Column(String) block_start = Column(Integer) tx_start = Column(Integer) block_cursor = Column(Integer) tx_cursor = Column(Integer) block_target = Column(Integer) date_created = Column(DateTime, default=datetime.datetime.utcnow) date_updated = Column(DateTime) @staticmethod def first(chain, session=None): """Check if a sync session for the specified chain already exists. :param chain: Chain spec string representation :type chain: str :param session: Session to use. If not specified, a separate session will be created for this method only. :type session: SqlAlchemy Session :returns: True if sync record found :rtype: bool """ local_session = False if session == None: session = SessionBase.create_session() local_session = True q = session.query(BlockchainSync.id) q = q.filter(BlockchainSync.blockchain==chain) o = q.first() if local_session: session.close() return o == None @staticmethod def get_last_live_height(current, session=None): """Get the most recent open-ended ("live") syncer record. :param current: Current block number :type current: number :param session: Session to use. If not specified, a separate session will be created for this method only. :type session: SqlAlchemy Session :returns: Block and transaction number, respectively :rtype: tuple """ local_session = False if session == None: session = SessionBase.create_session() local_session = True q = session.query(BlockchainSync) q = q.filter(BlockchainSync.block_target==None) q = q.order_by(BlockchainSync.date_created.desc()) o = q.first() if local_session: session.close() if o == None: return (0, 0) return (o.block_cursor, o.tx_cursor) @staticmethod def get_unsynced(session=None): """Get previous bounded sync sessions that did not complete. :param session: Session to use. If not specified, a separate session will be created for this method only. :type session: SqlAlchemy Session :returns: Syncer database ids :rtype: tuple, where first element is id """ unsynced = [] local_session = False if session == None: session = SessionBase.create_session() local_session = True q = session.query(BlockchainSync.id) q = q.filter(BlockchainSync.block_target!=None) q = q.filter(BlockchainSync.block_cursor<BlockchainSync.block_target) q = q.order_by(BlockchainSync.date_created.asc()) for u in q.all(): unsynced.append(u[0]) if local_session: session.close() return unsynced def set(self, block_height, tx_height): """Set the height of the syncer instance. Only manipulates object, does not transaction or commit to backend. :param block_height: Block number :type block_height: number :param tx_height: Block transaction number :type tx_height: number """ self.block_cursor = block_height self.tx_cursor = tx_height def cursor(self): """Get current state of cursor from cached instance. :returns: Block and transaction height, respectively :rtype: tuple """ return (self.block_cursor, self.tx_cursor) def start(self): """Get sync block start position from cached instance. :returns: Block and transaction height, respectively :rtype: tuple """ return (self.block_start, self.tx_start) def target(self): """Get sync block upper bound from cached instance. :returns: Block number :rtype: number, or None if sync is open-ended """ return self.block_target def chain(self): """Get chain the cached instance represents. """ return self.blockchain def __init__(self, chain, block_start, tx_start, block_target=None): self.blockchain = chain self.block_start = block_start self.tx_start = tx_start self.block_cursor = block_start self.tx_cursor = tx_start self.block_target = block_target self.date_created = datetime.datetime.utcnow() self.date_modified = datetime.datetime.utcnow()