@@ -2,6 +2,7 @@ class BlockValidator
22 attr_reader :errors , :stats
33
44 # Exception for transient errors that should trigger retries
5+ # This is informational - all exceptions are treated as transient
56 class TransientValidationError < StandardError ; end
67
78 def initialize
@@ -117,15 +118,10 @@ def load_genesis_transaction_hashes
117118
118119 def fetch_expected_data ( l1_block_number )
119120 EthscriptionsApiClient . fetch_block_data ( l1_block_number )
120- rescue EthscriptionsApiClient ::ApiUnavailableError => e
121- # API unavailable after exhausting all retries - this is an infrastructure issue
122- Rails . logger . warn "API unavailable for block #{ l1_block_number } : #{ e . message } "
123- raise TransientValidationError , e . message
124121 rescue => e
125- # Other unexpected errors - log and continue with empty data
126- message = "Unexpected error fetching API data: #{ e . message } "
127- @errors << message
128- { creations : [ ] , transfers : [ ] }
122+ # Treat any API client failure as transient to avoid false negatives
123+ Rails . logger . warn "Transient API error for block #{ l1_block_number } : #{ e . class } : #{ e . message } "
124+ raise TransientValidationError , e . message
129125 end
130126
131127 def aggregate_l2_events ( block_hashes )
@@ -137,24 +133,20 @@ def aggregate_l2_events(block_hashes)
137133 begin
138134 receipts = EthRpcClient . l2 . call ( 'eth_getBlockReceipts' , [ block_hash ] )
139135 if receipts . nil?
136+ # Treat missing receipts as transient infrastructure issue
140137 error_msg = "No receipts returned for L2 block #{ block_hash } "
141- @errors << error_msg
142- # Treat missing receipts as potentially transient
138+ Rails . logger . warn "Transient L2 error: #{ error_msg } "
143139 raise TransientValidationError , error_msg
144140 end
145141
146142 data = EventDecoder . decode_block_receipts ( receipts )
147143 all_creations . concat ( data [ :creations ] )
148144 all_transfers . concat ( data [ :transfers ] ) # Ethscriptions protocol transfers
149145 rescue => e
146+ # Treat any L2 RPC failure as transient to avoid false negatives
150147 error_msg = "Failed to get receipts for block #{ block_hash } : #{ e . message } "
151- @errors << error_msg
152- # Classify L2 receipt fetch errors - network issues are transient
153- if transient_error? ( e )
154- raise TransientValidationError , error_msg
155- else
156- raise
157- end
148+ Rails . logger . warn "Transient L2 error: #{ error_msg } "
149+ raise TransientValidationError , error_msg
158150 end
159151 end
160152
@@ -348,14 +340,15 @@ def verify_ethscription_storage(creation, l1_block_num, block_tag)
348340 begin
349341 stored = StorageReader . get_ethscription_with_content ( tx_hash , block_tag : block_tag )
350342 rescue => e
351- @errors << "Ethscription #{ tx_hash } not found in contract storage: #{ e . message } "
352- @storage_checks_performed . increment
353- return
343+ # RPC/network error - treat as transient inability to validate
344+ Rails . logger . warn "Transient storage error for #{ tx_hash } : #{ e . message } "
345+ raise TransientValidationError , "Storage read failed for #{ tx_hash } : #{ e . message } "
354346 end
355347
356348 @storage_checks_performed . increment
357349
358350 if stored . nil?
351+ # Ethscription genuinely doesn't exist in contract - this is a validation failure
359352 @errors << "Ethscription #{ tx_hash } not found in contract storage"
360353 return
361354 end
@@ -509,18 +502,32 @@ def verify_transfer_ownership(transfers, block_tag)
509502 # Verify each token's final owner
510503 final_owners . each do |token_id , expected_owner |
511504 # First check if the ethscription exists in storage
512- ethscription = StorageReader . get_ethscription ( token_id , block_tag : block_tag )
505+ begin
506+ ethscription = StorageReader . get_ethscription ( token_id , block_tag : block_tag )
507+ rescue => e
508+ # RPC/network error - treat as transient inability to validate
509+ Rails . logger . warn "Transient storage error for #{ token_id } : #{ e . message } "
510+ raise TransientValidationError , "Storage read failed for #{ token_id } : #{ e . message } "
511+ end
513512
514513 if ethscription . nil?
515- # Token doesn't exist yet - treat as fatal divergence
514+ # Token genuinely doesn't exist - this is a validation failure
516515 @errors << "Token #{ token_id } not found in storage"
517516 next
518517 end
519518
520- actual_owner = StorageReader . get_owner ( token_id , block_tag : block_tag )
519+ begin
520+ actual_owner = StorageReader . get_owner ( token_id , block_tag : block_tag )
521+ rescue => e
522+ # RPC/network error - treat as transient inability to validate
523+ Rails . logger . warn "Transient owner read error for #{ token_id } : #{ e . message } "
524+ raise TransientValidationError , "Owner read failed for #{ token_id } : #{ e . message } "
525+ end
526+
521527 @storage_checks_performed . increment
522528
523529 if actual_owner . nil?
530+ # Owner doesn't exist (shouldn't happen if ethscription exists) - validation failure
524531 @errors << "Could not verify owner of token #{ token_id } "
525532 next
526533 end
@@ -570,20 +577,6 @@ def safe_content_preview(content, length: 50)
570577 preview + ( content . length > length ? "..." : "" )
571578 end
572579
573- # Classify L2 RPC errors as transient (infrastructure) vs permanent (logic)
574- def transient_error? ( error )
575- case error
576- # L2 RPC network errors
577- when SocketError , Errno ::ECONNREFUSED , Errno ::ECONNRESET ,
578- Net ::OpenTimeout , Net ::ReadTimeout
579- true
580- # L2 RPC client errors that might be transient
581- when EthRpcClient ::HttpError , EthRpcClient ::ApiError
582- true
583- else
584- false
585- end
586- end
587580
588581 # Sanitize data structures for JSON serialization
589582 def sanitize_for_json ( data )
@@ -604,4 +597,3 @@ def sanitize_for_json(data)
604597 end
605598 end
606599end
607-
0 commit comments