Skip to content

Commit c168c91

Browse files
committed
Validation fixes
1 parent 1e5c78f commit c168c91

File tree

7 files changed

+79
-251
lines changed

7 files changed

+79
-251
lines changed

app/models/validation_result.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,33 @@ class ValidationResult < ApplicationRecord
99
where("JSON_EXTRACT(validation_stats, '$.validation_details.expected_creations') > 0 OR JSON_EXTRACT(validation_stats, '$.validation_details.expected_transfers') > 0 OR JSON_EXTRACT(validation_stats, '$.validation_details.storage_checks') > 0")
1010
}
1111

12+
def self.delete_successes_older_than(older_than: 6.hours, batch_size: 2_000, sleep_between_batches: 0.3)
13+
cutoff =
14+
case older_than
15+
when ActiveSupport::Duration
16+
older_than.ago
17+
when Numeric
18+
Time.current - older_than
19+
when Time
20+
older_than
21+
else
22+
raise ArgumentError, "Unsupported older_than: #{older_than.class}"
23+
end
24+
25+
scope = successful.where('validated_at < ?', cutoff)
26+
total_deleted = 0
27+
28+
scope.in_batches(of: batch_size) do |relation|
29+
deleted = relation.delete_all
30+
break if deleted.zero?
31+
32+
total_deleted += deleted
33+
sleep(sleep_between_batches) if sleep_between_batches.positive?
34+
end
35+
36+
total_deleted
37+
end
38+
1239
# Class methods for validation management
1340
def self.last_validated_block
1441
maximum(:l1_block)

app/services/eth_block_importer.rb

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -198,19 +198,24 @@ def import_blocks_until_done
198198

199199
begin
200200
loop do
201+
ImportProfiler.start("import_blocks_until_done_loop")
201202
# Check for validation failures
203+
ImportProfiler.start("real_validation_failure_detected")
202204
if real_validation_failure_detected?
203205
failed_block = get_validation_failure_block
204206
logger.error "Import stopped due to validation failure at block #{failed_block}"
205207
raise ValidationFailureError.new("Validation failure detected at block #{failed_block}")
206208
end
209+
ImportProfiler.stop("real_validation_failure_detected")
207210

208211
# Check if validation is stalled
212+
ImportProfiler.start("validation_stalled")
209213
if validation_stalled?
210214
current_position = current_max_eth_block_number
211215
logger.warn "Import paused - validation is behind (current: #{current_position})"
212216
raise ValidationStalledError.new("Validation stalled - waiting for validation to catch up")
213217
end
218+
ImportProfiler.stop("validation_stalled")
214219

215220
block_number = next_block_to_import
216221

@@ -252,6 +257,8 @@ def import_blocks_until_done
252257
rescue => e
253258
logger.error "Import error: #{e.message}"
254259
raise e
260+
ensure
261+
ImportProfiler.stop("import_blocks_until_done_loop")
255262
end
256263
end
257264
end
@@ -383,9 +390,9 @@ def import_single_block(block_number)
383390
ValidationJob.perform_later(block_number, l2_block_hashes)
384391
end
385392

386-
ImportProfiler.stop("import_single_block")
387-
388393
[imported_ethscriptions_blocks, [eth_block]]
394+
ensure
395+
ImportProfiler.stop("import_single_block")
389396
end
390397

391398
def import_next_block
@@ -486,6 +493,8 @@ def cleanup_stale_validation_records
486493

487494
def report_import_stats(blocks_imported_count:, stats_start_time:, stats_start_block:,
488495
total_gas_used:, total_transactions:, imported_l2_blocks:, recent_batch_time:)
496+
ImportProfiler.start("report_import_stats")
497+
489498
elapsed_time = Time.current - stats_start_time
490499
current_block = current_max_eth_block_number
491500

@@ -521,7 +530,6 @@ def report_import_stats(blocks_imported_count:, stats_start_time:, stats_start_b
521530
if ENV.fetch('VALIDATION_ENABLED').casecmp?('true')
522531
last_validated = ValidationResult.last_validated_block || 0
523532
validation_lag = current_block - last_validated
524-
validation_stats = ValidationResult.validation_stats(since: 1.hour.ago)
525533

526534
lag_status = case validation_lag
527535
when 0..5 then "✅ CURRENT"
@@ -530,11 +538,7 @@ def report_import_stats(blocks_imported_count:, stats_start_time:, stats_start_b
530538
else "🔴 VERY BEHIND"
531539
end
532540

533-
if validation_stats[:total] > 0
534-
validation_line = "🔍 VALIDATION: #{lag_status} (#{validation_lag} behind) | #{validation_stats[:passed]}/#{validation_stats[:total]} passed (#{validation_stats[:pass_rate]}%)"
535-
else
536-
validation_line = "🔍 VALIDATION: #{lag_status} (#{validation_lag} behind) | No validations completed yet"
537-
end
541+
validation_line = "🔍 VALIDATION: #{lag_status} (#{validation_lag} behind)"
538542
else
539543
validation_line = "🔍 Validation: DISABLED"
540544
end
@@ -558,6 +562,7 @@ def report_import_stats(blocks_imported_count:, stats_start_time:, stats_start_b
558562
if ImportProfiler.enabled?
559563
logger.info ""
560564
logger.info "🔍 DETAILED PROFILER STATS:"
565+
ImportProfiler.stop("report_import_stats")
561566
ImportProfiler.report
562567
ImportProfiler.reset
563568
end
@@ -567,15 +572,6 @@ def report_import_stats(blocks_imported_count:, stats_start_time:, stats_start_b
567572

568573
public
569574

570-
def validation_summary
571-
return nil unless ENV.fetch('VALIDATION_ENABLED').casecmp?('true')
572-
573-
stats = ValidationResult.validation_stats(since: 1.hour.ago)
574-
return "No validations completed" if stats[:total] == 0
575-
576-
"#{stats[:passed]}/#{stats[:total]} passed (#{stats[:pass_rate]}%), #{stats[:failed]} failed"
577-
end
578-
579575
def shutdown
580576
@prefetcher&.shutdown
581577
end

config/derive_ethscriptions_blocks.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,6 @@ module Clockwork
135135
total_blocks_imported += blocks_imported
136136

137137
puts "[#{Time.now}] Imported #{blocks_imported} blocks (#{initial_block + 1} to #{final_block})"
138-
139-
# Show validation summary if enabled
140-
if ENV.fetch('VALIDATION_ENABLED').casecmp?('true')
141-
puts importer.validation_summary
142-
end
143138
else
144139
# We're caught up
145140
elapsed = (Time.now - start_time).round(2)

config/recurring.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ development:
1313
clear_solid_queue_finished_jobs:
1414
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
1515
schedule: every hour at minute 12
16+
purge_old_validation_successes:
17+
command: "ValidationResult.delete_successes_older_than"
18+
schedule: every hour at minute 43
1619

1720
production:
1821
clear_solid_queue_finished_jobs:
1922
command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)"
2023
schedule: every hour at minute 12
24+
purge_old_validation_successes:
25+
command: "ValidationResult.delete_successes_older_than"
26+
schedule: every hour at minute 43
2127

2228
# development:
2329
# validation_gap_detection:

lib/block_validator.rb

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,36 @@ def verify_ethscription_storage(creation, l1_block_num, block_tag)
477477
end
478478

479479
def verify_transfer_ownership(transfers, block_tag)
480-
# Group transfers by token to get final owner
480+
# Replay transfers in deterministic order to determine the true final owner
481481
final_owners = {}
482482

483-
transfers.each do |transfer|
483+
sorted_transfers = Array(transfers).each_with_index.sort_by do |transfer, original_index|
484+
block_number = transfer[:block_number]
485+
transaction_index = transfer[:transaction_index]
486+
487+
if block_number.nil? || transaction_index.nil?
488+
raise "Transfer missing ordering metadata: #{transfer.inspect}"
489+
end
490+
491+
log_index = transfer[:event_log_index] || transfer[:log_index]
492+
493+
[
494+
block_number.to_i,
495+
transaction_index.to_i,
496+
log_index.nil? ? -1 : log_index.to_i, # calldata transfers (no log) happen before events
497+
original_index
498+
]
499+
end.map { |transfer, _| transfer }
500+
501+
sorted_transfers.each do |transfer|
484502
token_id = transfer[:token_id]
485-
final_owners[token_id] = transfer[:to]
503+
to_address = transfer[:to]
504+
505+
if token_id.blank? || to_address.blank?
506+
raise "Transfer missing token_id or recipient: #{transfer.inspect}"
507+
end
508+
509+
final_owners[token_id.downcase] = to_address.downcase
486510
end
487511

488512
# Verify each token's final owner

lib/tasks/geth.rake

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace :geth do
2+
desc "Print the Geth init command"
3+
task :init_command => :environment do
4+
puts GethDriver.init_command
5+
end
6+
end

0 commit comments

Comments
 (0)