1
1
from shared .clickhouse .batch_insert import buffer_insert , flush_buffer , batch_insert_into_clickhouse_table
2
- from shared .substrate import get_substrate_client
2
+ from shared .substrate import get_substrate_client , reconnect_substrate
3
3
from time import sleep
4
4
from shared .clickhouse .utils import (
5
5
get_clickhouse_client ,
6
6
table_exists ,
7
7
)
8
+ from shared .exceptions import DatabaseConnectionError , ShovelProcessingError
8
9
from tqdm import tqdm
9
10
import logging
10
11
import threading
11
12
from concurrent .futures import ThreadPoolExecutor
13
+ import sys
12
14
13
15
14
16
class ShovelBaseClass :
15
17
checkpoint_block_number = 0
16
18
last_buffer_flush_call_block_number = 0
17
19
name = None
18
20
skip_interval = 1
21
+ MAX_RETRIES = 3
22
+ RETRY_DELAY = 5
19
23
20
24
def __init__ (self , name , skip_interval = 1 ):
21
25
"""
@@ -26,49 +30,80 @@ def __init__(self, name, skip_interval=1):
26
30
self .starting_block = 0 # Default value, can be overridden by subclasses
27
31
28
32
def start (self ):
29
- print ("Initialising Substrate client" )
30
- substrate = get_substrate_client ()
31
-
32
- print ("Fetching the finalized block" )
33
- finalized_block_hash = substrate .get_chain_finalised_head ()
34
- finalized_block_number = substrate .get_block_number (
35
- finalized_block_hash )
36
-
37
- # Start the clickhouse buffer
38
- print ("Starting Clickhouse buffer" )
39
- executor = ThreadPoolExecutor (max_workers = 1 )
40
- threading .Thread (
41
- target = flush_buffer ,
42
- args = (executor , self ._buffer_flush_started , self ._buffer_flush_done ),
43
- ).start ()
44
-
45
- last_scraped_block_number = self .get_checkpoint ()
46
- logging .info (f"Last scraped block is { last_scraped_block_number } " )
47
-
48
- # Create a list of block numbers to scrape
33
+ retry_count = 0
49
34
while True :
50
- block_numbers = tqdm (
51
- range (last_scraped_block_number +
52
- 1 , finalized_block_number + 1 , self .skip_interval )
53
- )
54
-
55
- if len (block_numbers ) > 0 :
56
- logging .info (
57
- f"Catching up { len (block_numbers )} blocks" )
58
- for block_number in block_numbers :
59
- self .process_block (block_number )
60
- self .checkpoint_block_number = block_number
61
- else :
62
- logging .info (
63
- "Already up to latest finalized block, checking again in 12s..." )
64
-
65
- # Make sure to sleep so buffer with checkpoint update is flushed to Clickhouse
66
- # before trying again
67
- sleep (12 )
68
- last_scraped_block_number = self .get_checkpoint ()
69
- finalized_block_hash = substrate .get_chain_finalised_head ()
70
- finalized_block_number = substrate .get_block_number (
71
- finalized_block_hash )
35
+ try :
36
+ print ("Initialising Substrate client" )
37
+ substrate = get_substrate_client ()
38
+
39
+ print ("Fetching the finalized block" )
40
+ finalized_block_hash = substrate .get_chain_finalised_head ()
41
+ finalized_block_number = substrate .get_block_number (finalized_block_hash )
42
+
43
+ # Start the clickhouse buffer
44
+ print ("Starting Clickhouse buffer" )
45
+ executor = ThreadPoolExecutor (max_workers = 1 )
46
+ buffer_thread = threading .Thread (
47
+ target = flush_buffer ,
48
+ args = (executor , self ._buffer_flush_started , self ._buffer_flush_done ),
49
+ daemon = True # Make it a daemon thread so it exits with the main thread
50
+ )
51
+ buffer_thread .start ()
52
+
53
+ last_scraped_block_number = self .get_checkpoint ()
54
+ logging .info (f"Last scraped block is { last_scraped_block_number } " )
55
+
56
+ # Create a list of block numbers to scrape
57
+ while True :
58
+ try :
59
+ block_numbers = list (range (
60
+ last_scraped_block_number + 1 ,
61
+ finalized_block_number + 1 ,
62
+ self .skip_interval
63
+ ))
64
+
65
+ if len (block_numbers ) > 0 :
66
+ logging .info (f"Catching up { len (block_numbers )} blocks" )
67
+ for block_number in tqdm (block_numbers ):
68
+ try :
69
+ self .process_block (block_number )
70
+ self .checkpoint_block_number = block_number
71
+ except DatabaseConnectionError as e :
72
+ logging .error (f"Database connection error while processing block { block_number } : { str (e )} " )
73
+ raise # Re-raise to be caught by outer try-except
74
+ except Exception as e :
75
+ logging .error (f"Fatal error while processing block { block_number } : { str (e )} " )
76
+ raise ShovelProcessingError (f"Failed to process block { block_number } : { str (e )} " )
77
+ else :
78
+ logging .info ("Already up to latest finalized block, checking again in 12s..." )
79
+
80
+ # Reset retry count on successful iteration
81
+ retry_count = 0
82
+
83
+ # Make sure to sleep so buffer with checkpoint update is flushed to Clickhouse
84
+ sleep (12 )
85
+ last_scraped_block_number = self .get_checkpoint ()
86
+ finalized_block_hash = substrate .get_chain_finalised_head ()
87
+ finalized_block_number = substrate .get_block_number (finalized_block_hash )
88
+
89
+ except DatabaseConnectionError as e :
90
+ retry_count += 1
91
+ if retry_count > self .MAX_RETRIES :
92
+ logging .error (f"Max retries ({ self .MAX_RETRIES } ) exceeded for database connection. Exiting." )
93
+ raise ShovelProcessingError ("Max database connection retries exceeded" )
94
+
95
+ logging .warning (f"Database connection error (attempt { retry_count } /{ self .MAX_RETRIES } ): { str (e )} " )
96
+ logging .info (f"Retrying in { self .RETRY_DELAY } seconds..." )
97
+ sleep (self .RETRY_DELAY )
98
+ reconnect_substrate () # Try to reconnect to substrate
99
+ continue
100
+
101
+ except ShovelProcessingError as e :
102
+ logging .error (f"Fatal shovel error: { str (e )} " )
103
+ sys .exit (1 )
104
+ except Exception as e :
105
+ logging .error (f"Unexpected error: { str (e )} " )
106
+ sys .exit (1 )
72
107
73
108
def process_block (self , n ):
74
109
raise NotImplementedError (
@@ -106,7 +141,18 @@ def _buffer_flush_done(self, tables, rows):
106
141
107
142
def get_checkpoint (self ):
108
143
if not table_exists ("shovel_checkpoints" ):
109
- return self .starting_block - 1
144
+ return max (0 , self .starting_block - 1 )
145
+
146
+ # First check if our shovel has any entries
147
+ query = f"""
148
+ SELECT count(*)
149
+ FROM shovel_checkpoints
150
+ WHERE shovel_name = '{ self .name } '
151
+ """
152
+ count = get_clickhouse_client ().execute (query )[0 ][0 ]
153
+ if count == 0 :
154
+ return max (0 , self .starting_block - 1 )
155
+
110
156
query = f"""
111
157
SELECT block_number
112
158
FROM shovel_checkpoints
@@ -118,4 +164,4 @@ def get_checkpoint(self):
118
164
if res :
119
165
return res [0 ][0 ]
120
166
else :
121
- return self .starting_block - 1
167
+ return max ( 0 , self .starting_block - 1 ) # This case shouldn't happen due to count check above
0 commit comments