forked from Cardano-Fans/crfa-block-checker
-
Notifications
You must be signed in to change notification settings - Fork 0
/
block_checker.rb
199 lines (162 loc) · 5.1 KB
/
block_checker.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
require 'time'
require 'yaml'
require 'optparse'
require 'ostruct'
require 'bundler/setup'
Bundler.require(:default) # require all bundled gems
Dotenv.load
class BlockChecker
SHELLY_SLOT = 4492800
SHELLEY_EPOCH = 208
SLOTS_PER_EPOCH = 432000
attr_reader :assigned_slots_count
attr_reader :minted_blocks
attr_reader :lost_height_battles
attr_reader :lost_slot_battles
attr_reader :future_mints
attr_reader :unknown_block_status
attr_reader :performance
def initialize
@assigned_slots = {}
@minted_blocks = {}
@lost_height_battles = {}
@lost_slot_battles = {}
@future_mints = {}
@unknown_block_status = {}
@performance = {}
build_data
end
def options
return @options if @options
@options = OpenStruct.new
OptionParser.new do |opt|
opt.on('e', '--epoch=412') { |o| @options.epoch = o }
opt.on('p', '--pool-id=xyz') { |o| @options.poolid = o }
end.parse!
@options
end
def input_file
return @input_file if @input_file
if options.epoch
path = Dir["./epochs/#{options.epoch}*"].last
else
path = Dir["./epochs/*"].sort.last
end
@input_file = File.open(path.to_s)
end
def pool_id
@pool_id ||= options.poolid || ENV.fetch('POOL_ID')
end
def epoch_no
@epoch_no ||= File.basename(input_file.path, ".*").to_i
end
def epoch_running?
@epoch_running ||= (epoch_no == epoch_for_slot(latest_slot))
end
def epoch_for_slot(slot)
SHELLEY_EPOCH + ((slot - SHELLY_SLOT) / SLOTS_PER_EPOCH)
end
def latest_slot
@latest_slot ||= blockfrost_client.get_block_latest.dig(:body, :slot)
end
def blockfrost_client
@blockfrost_client ||= Blockfrostruby::CardanoMainNet.new(ENV.fetch 'BLOCKFROST_MAINNET_KEY')
end
def assigned_slots
return @assigned_slots if @assigned_slots.any?
assign_slots_from_cncli_json
rescue JSON::ParserError
assign_slots_from_cardano_cli
ensure
input_file.close
end
def assign_slots_from_cncli_json
# `cncli leaderlog` format
cncli_leaderlogs = JSON.parse(input_file.read)
cncli_leaderlogs["assignedSlots"].each do |slot|
@assigned_slots[slot["slot"]] = Time.parse(slot["at"]).utc
end
@assigned_slots_count = @assigned_slots.size
@assigned_slots
end
def assign_slots_from_cardano_cli
# `cardano-cli query leadership-schedule` format
File.foreach(input_file.path).with_index do |line, index|
if index == 0
raise StandardError, "Corrupt file format - #{input_file.path}" unless line.match /^\s*SlotNo/
end
m = line.match /^\s*(?<slot>\d*)\s*(?<time>.*)/
if m && !m[:slot].empty?
@assigned_slots[m[:slot].to_i] = Time.parse(m[:time]).utc
end
end
@assigned_slots_count = @assigned_slots.size
@assigned_slots
end
def build_data
assigned_slots.each do |slot, slot_time|
block = blockfrost_client.get_block_in_slot(slot)
status = block[:status]
if slot < latest_slot # it makes sense to check only past slots
if status == 200
slot_leader_pool_id = block[:body][:slot_leader]
unless slot_leader_pool_id == pool_id
lost_slot_battles[slot] = [slot_leader_pool_id, slot_time]
else
minted_blocks[slot] = slot_time
end
elsif status == 404
lost_height_battles[slot] = slot_time
else
unknown_block_status[slot] = slot_time
end
else
future_mints[slot] = slot_time
end
end
past_assigned_slots_count = assigned_slots_count - future_mints.size
performance[:minted_blocks] = (minted_blocks.size.to_f / past_assigned_slots_count * 100).round(2)
performance[:lost_height_battles] = (lost_height_battles.size.to_f / past_assigned_slots_count * 100).round(2)
performance[:lost_slot_battles] = (lost_slot_battles.size.to_f / past_assigned_slots_count * 100).round(2)
end
def contributers
YAML.load(File.open("./contributers.yml"))
end
def summary_output
if assigned_slots_count < 1
"No slots allocated for epoch #{epoch_no}"
else
[
"Assigned slots to mint blocks: #{assigned_slots_count.to_s}",
"Minted blocks: #{minted_blocks.size.to_s}",
"Lost height battles: #{lost_height_battles.size}",
"Lost slot battles: #{lost_slot_battles.size}"
].join("\n")
end
end
def future_mints_output
future_mints.map do |slot, slot_time|
"- #{slot_time.strftime('%d of %B, %Y')}"
end.join("\n")
end
def lost_slot_battles_output
lost_slot_battles.map do |slot, extra|
"- Block minted on slot #{slot} by pool #{extra[0]} at #{extra[1]}"
end.join("\n")
end
def lost_height_battles_output
lost_height_battles.map do |slot, slot_time|
"- Block ghosted on slot #{slot} at #{slot_time}"
end.join("\n")
end
def unknown_block_status_output
unknown_block_status.map do |slot, slot_time|
"- Slot #{slot} at #{slot_time}"
end.join("\n")
end
def performance_stats_output
performance.map do |perf, value|
"#{perf.to_s.gsub!(/_/, ' ').gsub(/^\w/) { $&.upcase }}: #{value}%"
end.join("\n")
end
end