2021-02-03 20:55:39 +01:00
|
|
|
# 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 .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
|
|
|
|
"""
|
2021-02-03 21:10:08 +01:00
|
|
|
__tablename__ = 'chain_sync'
|
2021-02-03 20:55:39 +01:00
|
|
|
|
|
|
|
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()
|