2021-02-03 19:40:03 +01:00
# standard imports
import uuid
import logging
2021-02-03 21:10:08 +01:00
import time
2021-04-04 15:03:58 +02:00
import signal
2021-04-16 15:21:26 +02:00
import json
2021-02-03 19:40:03 +01:00
2021-02-17 12:44:35 +01:00
# external imports
from chainlib . eth . block import (
block_by_number ,
Block ,
)
2021-04-16 15:21:26 +02:00
from chainlib . eth . tx import (
receipt ,
transaction ,
Tx ,
)
from chainlib . error import JSONRPCException
2021-02-17 12:44:35 +01:00
# local imports
from chainsyncer . filter import SyncFilter
2021-04-04 15:03:58 +02:00
from chainsyncer . error import (
SyncDone ,
NoBlockForYou ,
)
2021-02-17 12:44:35 +01:00
2021-04-15 14:11:06 +02:00
logg = logging . getLogger ( ) . getChild ( __name__ )
2021-02-03 19:40:03 +01:00
2021-04-09 16:07:10 +02:00
def noop_callback ( block , tx ) :
logg . debug ( ' noop callback ( {} , {} ) ' . format ( block , tx ) )
2021-02-11 10:14:28 +01:00
2021-02-03 19:40:03 +01:00
class Syncer :
2021-02-03 21:10:08 +01:00
running_global = True
2021-02-11 10:14:28 +01:00
yield_delay = 0.005
2021-04-15 14:11:06 +02:00
signal_request = [ signal . SIGINT , signal . SIGTERM ]
2021-04-04 15:03:58 +02:00
signal_set = False
2021-02-03 21:10:08 +01:00
2021-04-09 16:07:10 +02:00
def __init__ ( self , backend , pre_callback = None , block_callback = None , post_callback = None ) :
2021-02-03 20:55:39 +01:00
self . cursor = None
self . running = True
self . backend = backend
2021-02-18 23:55:49 +01:00
self . filter = SyncFilter ( backend )
2021-04-09 16:07:10 +02:00
self . block_callback = block_callback
self . pre_callback = pre_callback
self . post_callback = post_callback
2021-04-04 15:03:58 +02:00
if not Syncer . signal_set :
2021-04-15 14:11:06 +02:00
for sig in Syncer . signal_request :
signal . signal ( sig , Syncer . __sig_terminate )
2021-04-04 15:03:58 +02:00
Syncer . signal_set = True
@staticmethod
def __sig_terminate ( sig , frame ) :
logg . warning ( ' got signal {} ' . format ( sig ) )
Syncer . terminate ( )
@staticmethod
def terminate ( ) :
logg . info ( ' termination requested! ' )
Syncer . running_global = False
2021-02-03 20:55:39 +01:00
def chain ( self ) :
""" Returns the string representation of the chain spec for the chain the syncer is running on.
: returns : Chain spec string
: rtype : str
"""
return self . bc_cache . chain ( )
2021-02-03 19:40:03 +01:00
2021-02-11 09:02:17 +01:00
def add_filter ( self , f ) :
2021-02-17 12:44:35 +01:00
self . filter . add ( f )
2021-04-04 15:03:58 +02:00
self . backend . register_filter ( str ( f ) )
2021-02-11 09:02:17 +01:00
2021-02-03 19:40:03 +01:00
2021-04-15 11:15:48 +02:00
def process_single ( self , conn , block , tx ) :
self . backend . set ( block . number , tx . index )
2021-04-15 09:59:45 +02:00
self . filter . apply ( conn , block , tx )
2021-04-15 14:11:06 +02:00
2021-02-20 21:11:09 +01:00
class BlockPollSyncer ( Syncer ) :
2021-02-03 19:40:03 +01:00
2021-04-09 16:07:10 +02:00
def __init__ ( self , backend , pre_callback = None , block_callback = None , post_callback = None ) :
super ( BlockPollSyncer , self ) . __init__ ( backend , pre_callback , block_callback , post_callback )
2021-02-03 19:40:03 +01:00
2021-02-17 12:44:35 +01:00
def loop ( self , interval , conn ) :
2021-04-15 14:11:06 +02:00
( pair , fltr ) = self . backend . get ( )
start_tx = pair [ 1 ]
2021-02-03 20:55:39 +01:00
while self . running and Syncer . running_global :
2021-04-09 16:07:10 +02:00
if self . pre_callback != None :
self . pre_callback ( )
2021-04-04 15:03:58 +02:00
while True and Syncer . running_global :
2021-04-15 15:27:19 +02:00
if start_tx > 0 :
start_tx - = 1
continue
2021-02-17 12:44:35 +01:00
try :
block = self . get ( conn )
2021-04-04 15:03:58 +02:00
except SyncDone as e :
logg . info ( ' sync done: {} ' . format ( e ) )
return self . backend . get ( )
except NoBlockForYou as e :
2021-02-09 23:26:48 +01:00
break
2021-04-04 15:03:58 +02:00
# TODO: To properly handle this, ensure that previous request is rolled back
# except sqlalchemy.exc.OperationalError as e:
# logg.error('database error: {}'.format(e))
# break
2021-04-09 16:07:10 +02:00
if self . block_callback != None :
self . block_callback ( block , None )
last_block = block
2021-02-17 12:44:35 +01:00
self . process ( conn , block )
2021-02-11 12:46:36 +01:00
start_tx = 0
2021-02-11 10:14:28 +01:00
time . sleep ( self . yield_delay )
2021-04-09 16:07:10 +02:00
if self . post_callback != None :
self . post_callback ( )
2021-02-03 21:10:08 +01:00
time . sleep ( interval )
2021-02-03 20:55:39 +01:00
2021-02-03 19:40:03 +01:00
2021-02-20 21:11:09 +01:00
class HeadSyncer ( BlockPollSyncer ) :
2021-02-03 19:40:03 +01:00
2021-02-17 12:44:35 +01:00
def process ( self , conn , block ) :
2021-04-15 14:11:06 +02:00
( pair , fltr ) = self . backend . get ( )
2021-04-15 15:27:19 +02:00
logg . debug ( ' process block {} (backend {} : {} ) ' . format ( block , pair , fltr ) )
2021-04-15 14:11:06 +02:00
i = pair [ 1 ] # set tx index from previous
2021-02-03 23:03:39 +01:00
tx = None
while True :
try :
tx = block . tx ( i )
2021-04-16 15:21:26 +02:00
except AttributeError :
o = transaction ( block . txs [ i ] )
r = conn . do ( o )
tx = Tx ( Tx . src_normalize ( r ) , block = block )
2021-02-03 23:03:39 +01:00
except IndexError as e :
2021-04-04 15:03:58 +02:00
logg . debug ( ' index error syncer rcpt get {} ' . format ( e ) )
2021-02-11 10:14:28 +01:00
self . backend . set ( block . number + 1 , 0 )
2021-02-03 23:03:39 +01:00
break
2021-04-15 09:59:45 +02:00
2021-04-16 15:21:26 +02:00
# TODO: Move specifics to eth subpackage, receipts are not a global concept
2021-04-15 09:59:45 +02:00
rcpt = conn . do ( receipt ( tx . hash ) )
2021-04-16 15:21:26 +02:00
if rcpt != None :
tx . apply_receipt ( Tx . src_normalize ( rcpt ) )
2021-04-15 09:59:45 +02:00
2021-04-15 11:15:48 +02:00
self . process_single ( conn , block , tx )
2021-04-15 15:27:19 +02:00
self . backend . reset_filter ( )
2021-04-15 09:59:45 +02:00
2021-02-03 23:03:39 +01:00
i + = 1
2021-02-17 12:44:35 +01:00
def get ( self , conn ) :
2021-04-04 15:03:58 +02:00
( height , flags ) = self . backend . get ( )
block_number = height [ 0 ]
block_hash = [ ]
o = block_by_number ( block_number )
r = conn . do ( o )
if r == None :
raise NoBlockForYou ( )
b = Block ( r )
2021-04-15 14:11:06 +02:00
b . txs = b . txs [ height [ 1 ] : ]
2021-04-04 15:03:58 +02:00
return b
def __str__ ( self ) :
return ' [headsyncer] {} ' . format ( str ( self . backend ) )
class HistorySyncer ( HeadSyncer ) :
2021-04-09 16:07:10 +02:00
def __init__ ( self , backend , pre_callback = None , block_callback = None , post_callback = None ) :
super ( HeadSyncer , self ) . __init__ ( backend , pre_callback , block_callback , post_callback )
2021-04-04 15:03:58 +02:00
self . block_target = None
( block_number , flags ) = self . backend . target ( )
if block_number == None :
raise AttributeError ( ' backend has no future target. Use HeadSyner instead ' )
self . block_target = block_number
2021-04-15 16:23:30 +02:00
logg . debug ( ' block target {} ' . format ( self . block_target ) )
2021-04-04 15:03:58 +02:00
def get ( self , conn ) :
( height , flags ) = self . backend . get ( )
if self . block_target < height [ 0 ] :
raise SyncDone ( self . block_target )
block_number = height [ 0 ]
2021-02-03 19:40:03 +01:00
block_hash = [ ]
2021-02-17 12:44:35 +01:00
o = block_by_number ( block_number )
2021-04-16 15:21:26 +02:00
try :
r = conn . do ( o )
# TODO: Disambiguate whether error is temporary or permanent, if permanent, SyncDone should be raised, because a historical sync is attempted into the future
except JSONRPCException :
r = None
2021-04-04 15:03:58 +02:00
if r == None :
2021-04-16 15:21:26 +02:00
raise SyncDone ( ) #NoBlockForYou()
2021-02-17 12:44:35 +01:00
b = Block ( r )
2021-02-03 19:40:03 +01:00
2021-02-17 12:44:35 +01:00
return b
2021-04-04 15:03:58 +02:00
def __str__ ( self ) :
return ' [historysyncer] {} ' . format ( str ( self . backend ) )