2021-08-26 10:09:47 +02:00
|
|
|
# standard imports
|
|
|
|
import uuid
|
|
|
|
import logging
|
|
|
|
import time
|
|
|
|
import signal
|
|
|
|
import json
|
|
|
|
|
|
|
|
# external imports
|
|
|
|
from chainlib.error import JSONRPCException
|
|
|
|
|
|
|
|
# local imports
|
|
|
|
from chainsyncer.filter import SyncFilter
|
|
|
|
from chainsyncer.error import (
|
|
|
|
SyncDone,
|
|
|
|
NoBlockForYou,
|
|
|
|
)
|
|
|
|
|
|
|
|
logg = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def noop_callback(block, tx):
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Logger-only callback for pre- and post processing.
|
|
|
|
|
|
|
|
:param block: Block object
|
|
|
|
:type block: chainlib.block.Block
|
|
|
|
:param tx: Transaction object
|
|
|
|
:type tx: chainlib.tx.Tx
|
|
|
|
"""
|
2021-08-26 10:09:47 +02:00
|
|
|
logg.debug('noop callback ({},{})'.format(block, tx))
|
|
|
|
|
|
|
|
|
|
|
|
class Syncer:
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Base class for syncer implementations.
|
|
|
|
|
|
|
|
:param backend: Syncer state backend
|
|
|
|
:type backend: chainsyncer.backend.base.Backend implementation
|
|
|
|
:param chain_interface: Chain interface implementation
|
|
|
|
:type chain_interface: chainlib.interface.ChainInterface implementation
|
|
|
|
:param pre_callback: Function to call before polling. Function will receive no arguments.
|
|
|
|
:type pre_callback: function
|
|
|
|
:param block_callback: Function to call before processing txs in a retrieved block. Function should have signature as chainsyncer.driver.base.noop_callback
|
|
|
|
:type block_callback: function
|
|
|
|
:param post_callback: Function to call after polling. Function will receive no arguments.
|
|
|
|
:type post_callback: function
|
|
|
|
"""
|
2021-08-26 10:09:47 +02:00
|
|
|
|
|
|
|
running_global = True
|
2021-08-27 13:41:03 +02:00
|
|
|
"""If set to false syncer will terminate polling loop."""
|
2021-08-26 10:09:47 +02:00
|
|
|
yield_delay=0.005
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Delay between each processed block."""
|
2021-08-26 10:09:47 +02:00
|
|
|
signal_request = [signal.SIGINT, signal.SIGTERM]
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Signals to catch to request shutdown."""
|
2021-08-26 10:09:47 +02:00
|
|
|
signal_set = False
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Whether shutdown signal has been received."""
|
2021-08-26 10:09:47 +02:00
|
|
|
name = 'base'
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Syncer name, to be overriden for each extended implementation."""
|
2021-08-26 10:09:47 +02:00
|
|
|
|
|
|
|
def __init__(self, backend, chain_interface, pre_callback=None, block_callback=None, post_callback=None):
|
|
|
|
self.chain_interface = chain_interface
|
|
|
|
self.cursor = None
|
|
|
|
self.running = True
|
|
|
|
self.backend = backend
|
|
|
|
self.filter = SyncFilter(backend)
|
|
|
|
self.block_callback = block_callback
|
|
|
|
self.pre_callback = pre_callback
|
|
|
|
self.post_callback = post_callback
|
|
|
|
if not Syncer.signal_set:
|
|
|
|
for sig in Syncer.signal_request:
|
|
|
|
signal.signal(sig, self.__sig_terminate)
|
|
|
|
Syncer.signal_set = True
|
|
|
|
|
|
|
|
|
|
|
|
def __sig_terminate(self, sig, frame):
|
|
|
|
logg.warning('got signal {}'.format(sig))
|
|
|
|
self.terminate()
|
|
|
|
|
|
|
|
|
|
|
|
def terminate(self):
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Set syncer to terminate as soon as possible.
|
|
|
|
"""
|
2021-08-26 10:09:47 +02:00
|
|
|
logg.info('termination requested!')
|
|
|
|
Syncer.running_global = False
|
2021-08-27 13:41:03 +02:00
|
|
|
self.running = False
|
2021-08-26 10:09:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
def add_filter(self, f):
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Add filter to be processed for each transaction.
|
|
|
|
|
|
|
|
:param f: Filter
|
|
|
|
:type f: Object instance implementing signature as in chainsyncer.filter.NoopFilter.filter
|
|
|
|
"""
|
2021-08-26 10:09:47 +02:00
|
|
|
self.filter.add(f)
|
|
|
|
self.backend.register_filter(str(f))
|
|
|
|
|
|
|
|
|
|
|
|
def process_single(self, conn, block, tx):
|
2021-08-27 13:41:03 +02:00
|
|
|
"""Set syncer backend cursor to the given transaction index and block height, and apply all registered filters on transaction.
|
|
|
|
|
|
|
|
:param conn: RPC connection instance
|
|
|
|
:type conn: chainlib.connection.RPCConnection
|
|
|
|
:param block: Block object
|
|
|
|
:type block: chainlib.block.Block
|
|
|
|
:param block: Transaction object
|
|
|
|
:type block: chainlib.tx.Tx
|
|
|
|
"""
|
2021-08-26 10:09:47 +02:00
|
|
|
self.backend.set(block.number, tx.index)
|
|
|
|
self.filter.apply(conn, block, tx)
|
|
|
|
|
2021-08-27 13:41:03 +02:00
|
|
|
|
|
|
|
def loop(self, interval, conn):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
|
|
def process(self, conn, block):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
|
|
|
def get(self, conn)
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
|
2021-08-26 10:09:47 +02:00
|
|
|
def __str__(self):
|
|
|
|
return 'syncer "{}" {}'.format(
|
|
|
|
self.name,
|
|
|
|
self.backend,
|
|
|
|
)
|