meeseeker 0.0.8 → 2.0.1.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +118 -89
- data/Rakefile +125 -45
- data/bin/meeseeker +2 -2
- data/lib/meeseeker.rb +178 -6
- data/lib/meeseeker/block_follower_job.rb +113 -65
- data/lib/meeseeker/hive_engine.rb +20 -0
- data/lib/meeseeker/steem_engine/agent.rb +33 -19
- data/lib/meeseeker/steem_engine/follower_job.rb +31 -14
- data/lib/meeseeker/version.rb +1 -1
- data/lib/meeseeker/witness_schedule_job.rb +6 -2
- data/meeseeker.gemspec +3 -1
- data/test/meeseeker/meeseeker_test.rb +261 -16
- data/test/test_helper.rb +11 -0
- metadata +66 -26
data/bin/meeseeker
CHANGED
@@ -32,7 +32,7 @@ when 'sync', 'witness:schedule'
|
|
32
32
|
Rake::Task['sync'].reenable
|
33
33
|
end; end
|
34
34
|
when 'find' then Rake::Task['find'].invoke(*ARGV[1..-1])
|
35
|
-
when 'reset' then Rake::Task['reset'].invoke
|
35
|
+
when 'reset' then Rake::Task['reset'].invoke(*ARGV[1..-1])
|
36
36
|
else
|
37
37
|
puts "\nBegin/resume sync:"
|
38
38
|
puts "\t#{filename} sync [chain] [block_num]\n\n"
|
@@ -44,7 +44,7 @@ else
|
|
44
44
|
puts "\t#{filename} find block 3044538"
|
45
45
|
puts "\t#{filename} find trx 983c5e5c6aef52f1647d952a18771f76b885e6de\n\n"
|
46
46
|
puts 'Clear all keys:'
|
47
|
-
puts "\t#{filename} reset\n\n"
|
47
|
+
puts "\t#{filename} reset [chain]\n\n"
|
48
48
|
puts "Note, using find and reset is not intended for routine operations. It is intended for analysis only."
|
49
49
|
puts "See: https://github.com/inertia186/meeseeker#usage"
|
50
50
|
end
|
data/lib/meeseeker.rb
CHANGED
@@ -1,28 +1,200 @@
|
|
1
1
|
require 'redis'
|
2
2
|
require 'steem'
|
3
|
+
require 'hive'
|
3
4
|
|
4
5
|
require 'meeseeker/version'
|
5
6
|
require 'meeseeker/block_follower_job'
|
6
7
|
require 'meeseeker/witness_schedule_job'
|
7
8
|
require 'meeseeker/steem_engine/agent'
|
8
9
|
require 'meeseeker/steem_engine/follower_job'
|
10
|
+
require 'meeseeker/hive_engine'
|
9
11
|
|
10
12
|
module Meeseeker
|
11
|
-
|
12
|
-
|
13
|
+
STEEM_CHAIN_ID = '0000000000000000000000000000000000000000000000000000000000000000'
|
14
|
+
HIVE_LEGACY_CHAIN_ID = '0000000000000000000000000000000000000000000000000000000000000000'
|
15
|
+
HIVE_CHAIN_ID = 'beeab0de00000000000000000000000000000000000000000000000000000000'
|
16
|
+
STEEM_CHAIN_KEY_PREFIX = 'steem'
|
17
|
+
HIVE_CHAIN_KEY_PREFIX = 'hive'
|
18
|
+
STEEM_ENGINE_CHAIN_KEY_PREFIX = 'steem_engine'
|
19
|
+
HIVE_ENGINE_CHAIN_KEY_PREFIX = 'hive_engine'
|
20
|
+
LAST_BLOCK_NUM_KEY_SUFFIX = ':meeseeker:last_block_num'
|
21
|
+
LAST_STEEM_ENGINE_BLOCK_NUM_KEY_SUFFIX = ':meeseeker:last_block_num'
|
13
22
|
BLOCKS_PER_DAY = 28800
|
14
23
|
VIRTUAL_TRX_ID = '0000000000000000000000000000000000000000'
|
24
|
+
BLOCK_INTERVAL = 3
|
25
|
+
SHUFFLE_URL = 'shuffle'
|
26
|
+
DEFAULT_STEEM_URL = 'https://api.steemit.com'
|
27
|
+
DEFAULT_STEEM_FAILOVER_URLS = [
|
28
|
+
DEFAULT_STEEM_URL,
|
29
|
+
# 'https://steemd.minnowsupportproject.org',
|
30
|
+
# 'https://anyx.io',
|
31
|
+
# 'http://anyx.io',
|
32
|
+
# 'https://steemd.privex.io',
|
33
|
+
# 'https://api.steem.house'
|
34
|
+
]
|
35
|
+
DEFAULT_HIVE_URL = 'https://api.openhive.network'
|
36
|
+
DEFAULT_HIVE_FAILOVER_URLS = [
|
37
|
+
DEFAULT_HIVE_URL,
|
38
|
+
'https://api.hivekings.com',
|
39
|
+
'https://anyx.io',
|
40
|
+
'http://anyx.io',
|
41
|
+
'https://techcoderx.com',
|
42
|
+
'https://rpc.esteem.app',
|
43
|
+
'https://hived.privex.io',
|
44
|
+
'https://api.pharesim.me',
|
45
|
+
'https://api.hive.blog',
|
46
|
+
'https://rpc.ausbit.dev'
|
47
|
+
]
|
48
|
+
|
49
|
+
def default_chain_key_prefix
|
50
|
+
ENV.fetch('MEESEEKER_CHAIN_KEY_PREFIX', chain_key_prefix)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.chain_key_prefix
|
54
|
+
@chain_key_prefix ||= {}
|
55
|
+
url = default_url(HIVE_CHAIN_KEY_PREFIX)
|
56
|
+
|
57
|
+
return @chain_key_prefix[url] if !!@chain_key_prefix[url]
|
58
|
+
|
59
|
+
# Just use the Hive API for either chain, until we know which one we're
|
60
|
+
# using.
|
61
|
+
api = Hive::DatabaseApi.new(url: url)
|
62
|
+
|
63
|
+
api.get_config do |config|
|
64
|
+
@chain_key_prefix[node_url] = if !!config.HIVE_CHAIN_ID && config.HIVE_CHAIN_ID == HIVE_CHAIN_ID
|
65
|
+
HIVE_CHAIN_KEY_PREFIX
|
66
|
+
elsif !!config.HIVE_CHAIN_ID && config.HIVE_CHAIN_ID == HIVE_LEGACY_CHAIN_ID
|
67
|
+
HIVE_CHAIN_KEY_PREFIX
|
68
|
+
elsif !!config.STEEM_CHAIN_ID && config.STEEM_CHAIN_ID == STEEM_CHAIN_ID
|
69
|
+
STEEM_CHAIN_KEY_PREFIX
|
70
|
+
else
|
71
|
+
config.keys.find{|k| k.end_with? '_CHAIN_ID'}.split('_').first.downcase.tap do |guess|
|
72
|
+
warn "Guessing chain_key_prefix = '#{guess}' for unknown chain on: #{node_url}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.default_url(chain = default_chain_key_prefix)
|
79
|
+
ENV.fetch('MEESEEKER_NODE_URL') do
|
80
|
+
case chain.to_s
|
81
|
+
when STEEM_CHAIN_KEY_PREFIX then DEFAULT_STEEM_URL
|
82
|
+
when HIVE_CHAIN_KEY_PREFIX then DEFAULT_HIVE_URL
|
83
|
+
else
|
84
|
+
raise "Unknown chain: #{chain}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@problem_node_urls = []
|
90
|
+
|
15
91
|
@redis = Redis.new(url: ENV.fetch('MEESEEKER_REDIS_URL', 'redis://127.0.0.1:6379/0'))
|
16
|
-
@node_url = ENV.fetch('
|
92
|
+
@node_url = default_url(ENV.fetch('MEESEEKER_CHAIN_KEY_PREFIX', HIVE_CHAIN_KEY_PREFIX))
|
17
93
|
@steem_engine_node_url = ENV.fetch('MEESEEKER_STEEM_ENGINE_NODE_URL', 'https://api.steem-engine.com/rpc')
|
94
|
+
@hive_engine_node_url = ENV.fetch('MEESEEKER_HIVE_ENGINE_NODE_URL', 'https://api.hive-engine.com/rpc')
|
18
95
|
@stream_mode = ENV.fetch('MEESEEKER_STREAM_MODE', 'head').downcase.to_sym
|
19
96
|
@include_virtual = ENV.fetch('MEESEEKER_INCLUDE_VIRTUAL', 'true').downcase == 'true'
|
20
97
|
@include_block_header = ENV.fetch('MEESEEKER_INCLUDE_BLOCK_HEADER', 'true').downcase == 'true'
|
21
98
|
@publish_op_custom_id = ENV.fetch('MEESEEKER_PUBLISH_OP_CUSTOM_ID', 'false').downcase == 'true'
|
22
|
-
@expire_keys = ENV.fetch('MEESEEKER_EXPIRE_KEYS', BLOCKS_PER_DAY *
|
99
|
+
@expire_keys = ENV.fetch('MEESEEKER_EXPIRE_KEYS', BLOCKS_PER_DAY * BLOCK_INTERVAL).to_i
|
100
|
+
@max_keys = ENV.fetch('MEESEEKER_MAX_KEYS', '-1').to_i
|
23
101
|
|
24
102
|
extend self
|
25
103
|
|
26
|
-
attr_accessor :redis, :node_url, :steem_engine_node_url,
|
27
|
-
:
|
104
|
+
attr_accessor :redis, :node_url, :steem_engine_node_url,
|
105
|
+
:hive_engine_node_url, :expire_keys, :max_keys, :stream_mode,
|
106
|
+
:include_virtual, :include_block_header, :publish_op_custom_id
|
107
|
+
|
108
|
+
def self.shuffle_node_url(chain = ENV.fetch('MEESEEKER_CHAIN_KEY_PREFIX', HIVE_CHAIN_KEY_PREFIX))
|
109
|
+
chain = chain.to_s
|
110
|
+
node_url = ENV.fetch('MEESEEKER_NODE_URL', default_url(ENV.fetch('MEESEEKER_CHAIN_KEY_PREFIX', chain)))
|
111
|
+
return node_url unless node_url == SHUFFLE_URL
|
112
|
+
|
113
|
+
@problem_node_urls = [] if rand(1..1000) == 13
|
114
|
+
shuffle_node_url!(chain)
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.api_class(chain = default_chain_key_prefix)
|
118
|
+
case chain.to_s
|
119
|
+
when STEEM_CHAIN_KEY_PREFIX then Steem::Api
|
120
|
+
when HIVE_CHAIN_KEY_PREFIX then Hive::Api
|
121
|
+
else
|
122
|
+
raise "Unknown chain: #{chain}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.condenser_api_class(chain = default_chain_key_prefix)
|
127
|
+
case chain.to_s
|
128
|
+
when STEEM_CHAIN_KEY_PREFIX then Steem::CondenserApi
|
129
|
+
when HIVE_CHAIN_KEY_PREFIX then Hive::CondenserApi
|
130
|
+
else
|
131
|
+
raise "Unknown chain: #{chain}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.block_api_class(chain = default_chain_key_prefix)
|
136
|
+
case chain.to_s
|
137
|
+
when STEEM_CHAIN_KEY_PREFIX then Steem::BlockApi
|
138
|
+
when HIVE_CHAIN_KEY_PREFIX then Hive::BlockApi
|
139
|
+
else
|
140
|
+
raise "Unknown chain: #{chain}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def self.database_api_class(chain = default_chain_key_prefix)
|
145
|
+
case chain.to_s
|
146
|
+
when STEEM_CHAIN_KEY_PREFIX then Steem::DatabaseApi
|
147
|
+
when HIVE_CHAIN_KEY_PREFIX then Hive::DatabaseApi
|
148
|
+
else
|
149
|
+
raise "Unknown chain: #{chain}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.stream_class(chain = default_chain_key_prefix)
|
154
|
+
case chain.to_s
|
155
|
+
when STEEM_CHAIN_KEY_PREFIX then Steem::Stream
|
156
|
+
when HIVE_CHAIN_KEY_PREFIX then Hive::Stream
|
157
|
+
else
|
158
|
+
raise "Unknown chain: #{chain}"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.shuffle_node_url!(chain = ENV.fetch('MEESEEKER_CHAIN_KEY_PREFIX', HIVE_CHAIN_KEY_PREFIX))
|
163
|
+
chain = chain.to_s
|
164
|
+
failover_urls = case chain
|
165
|
+
when STEEM_CHAIN_KEY_PREFIX then DEFAULT_STEEM_FAILOVER_URLS - @problem_node_urls
|
166
|
+
when HIVE_CHAIN_KEY_PREFIX then DEFAULT_HIVE_FAILOVER_URLS - @problem_node_urls
|
167
|
+
else; []
|
168
|
+
end
|
169
|
+
url = failover_urls.sample
|
170
|
+
api = api_class(chain).new(url: url)
|
171
|
+
|
172
|
+
api.get_accounts(['fullnodeupdate']) do |accounts|
|
173
|
+
fullnodeupdate = accounts.first
|
174
|
+
metadata = (JSON[fullnodeupdate.json_metadata] rescue nil) || {}
|
175
|
+
|
176
|
+
nodes = metadata.fetch('report', []).map do |report|
|
177
|
+
next if chain == HIVE_CHAIN_KEY_PREFIX && !report[HIVE_CHAIN_KEY_PREFIX]
|
178
|
+
next if chain != HIVE_CHAIN_KEY_PREFIX && !!report[HIVE_CHAIN_KEY_PREFIX]
|
179
|
+
|
180
|
+
report['node']
|
181
|
+
end.compact.uniq
|
182
|
+
|
183
|
+
nodes -= @problem_node_urls
|
184
|
+
|
185
|
+
if nodes.any?
|
186
|
+
nodes.sample
|
187
|
+
else
|
188
|
+
@node_url = failover_urls.sample
|
189
|
+
end
|
190
|
+
end
|
191
|
+
rescue => e
|
192
|
+
puts "#{url}: #{e}"
|
193
|
+
|
194
|
+
@problem_node_urls << url
|
195
|
+
failover_urls -= @problem_node_urls
|
196
|
+
failover_urls.sample
|
197
|
+
end
|
198
|
+
|
199
|
+
shuffle_node_url! if @node_url == SHUFFLE_URL
|
28
200
|
end
|
@@ -1,18 +1,24 @@
|
|
1
|
+
require 'radiator'
|
2
|
+
|
1
3
|
module Meeseeker
|
2
4
|
class BlockFollowerJob
|
3
5
|
MAX_VOP_RETRY = 3
|
4
6
|
|
5
7
|
def perform(options = {})
|
6
|
-
|
8
|
+
chain = (options[:chain] || 'hive').to_sym
|
9
|
+
url = Meeseeker.default_url(chain)
|
10
|
+
block_api = Meeseeker.block_api_class(chain).new(url: url)
|
7
11
|
redis = Meeseeker.redis
|
8
12
|
last_key_prefix = nil
|
9
13
|
trx_index = 0
|
10
14
|
current_block_num = nil
|
11
15
|
block_transactions = []
|
16
|
+
chain_key_prefix = chain.to_s if !!options[:chain]
|
17
|
+
chain_key_prefix ||= Meeseeker.default_chain_key_prefix
|
12
18
|
|
13
19
|
stream_operations(options) do |op, trx_id, block_num|
|
14
20
|
begin
|
15
|
-
current_key_prefix = "
|
21
|
+
current_key_prefix = "#{chain_key_prefix}:#{block_num}:#{trx_id}"
|
16
22
|
|
17
23
|
if current_key_prefix == last_key_prefix
|
18
24
|
trx_index += 1
|
@@ -26,17 +32,28 @@ module Meeseeker
|
|
26
32
|
}
|
27
33
|
|
28
34
|
block_transactions << trx_id unless trx_id == VIRTUAL_TRX_ID
|
29
|
-
redis.publish(
|
35
|
+
redis.publish("#{chain_key_prefix}:transaction", transaction_payload.to_json)
|
30
36
|
end
|
31
|
-
last_key_prefix = "
|
37
|
+
last_key_prefix = "#{chain_key_prefix}:#{block_num}:#{trx_id}"
|
32
38
|
trx_index = 0
|
33
39
|
end
|
34
40
|
|
35
|
-
op_type = op.type.
|
41
|
+
op_type = if op.type.end_with? '_operation'
|
42
|
+
op.type.split('_')[0..-2].join('_')
|
43
|
+
else
|
44
|
+
op.type
|
45
|
+
end
|
46
|
+
|
36
47
|
key = "#{current_key_prefix}:#{trx_index}:#{op_type}"
|
37
48
|
puts key
|
38
49
|
end
|
39
50
|
|
51
|
+
unless Meeseeker.max_keys == -1
|
52
|
+
while redis.keys("#{chain_key_prefix}:*").size > Meeseeker.max_keys
|
53
|
+
sleep Meeseeker::BLOCK_INTERVAL
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
40
57
|
redis.set(key, op.to_json)
|
41
58
|
redis.expire(key, Meeseeker.expire_keys) unless Meeseeker.expire_keys == -1
|
42
59
|
|
@@ -49,26 +66,30 @@ module Meeseeker
|
|
49
66
|
if Meeseeker.include_block_header
|
50
67
|
catch :block_header do
|
51
68
|
block_api.get_block_header(block_num: block_num) do |result|
|
52
|
-
|
69
|
+
if result.nil? || result.header.nil?
|
70
|
+
puts "Node returned empty result for block_header on block_num: #{block_num} (rate limiting?). Retrying ..."
|
71
|
+
sleep Meeseeker::BLOCK_INTERVAL
|
72
|
+
throw :block_header
|
73
|
+
end
|
53
74
|
|
54
75
|
block_payload.merge!(result.header.to_h)
|
55
76
|
end
|
56
77
|
end
|
57
78
|
end
|
58
79
|
|
59
|
-
redis.set(
|
60
|
-
redis.publish(
|
80
|
+
redis.set(chain_key_prefix + LAST_BLOCK_NUM_KEY_SUFFIX, block_num)
|
81
|
+
redis.publish("#{chain_key_prefix}:block", block_payload.to_json)
|
61
82
|
current_block_num = block_num
|
62
83
|
end
|
63
84
|
|
64
|
-
redis.publish("
|
85
|
+
redis.publish("#{chain_key_prefix}:op:#{op_type}", {key: key}.to_json)
|
65
86
|
|
66
87
|
if Meeseeker.publish_op_custom_id
|
67
88
|
if %w(custom custom_binary custom_json).include? op_type
|
68
89
|
id = (op["value"]["id"] rescue nil).to_s
|
69
90
|
|
70
91
|
if id.size > 0
|
71
|
-
redis.publish("
|
92
|
+
redis.publish("#{chain_key_prefix}:op:#{op_type}:#{id}", {key: key}.to_json)
|
72
93
|
end
|
73
94
|
end
|
74
95
|
end
|
@@ -76,7 +97,10 @@ module Meeseeker
|
|
76
97
|
end
|
77
98
|
private
|
78
99
|
def stream_operations(options = {}, &block)
|
100
|
+
chain = (options[:chain] || 'hive').to_sym
|
79
101
|
redis = Meeseeker.redis
|
102
|
+
chain_key_prefix = chain.to_s if !!options[:chain]
|
103
|
+
chain_key_prefix ||= Meeseeker.chain_key_prefix
|
80
104
|
last_block_num = nil
|
81
105
|
mode = options.delete(:mode) || Meeseeker.stream_mode
|
82
106
|
options[:include_virtual] ||= Meeseeker.include_virtual
|
@@ -84,8 +108,9 @@ module Meeseeker
|
|
84
108
|
if !!options[:at_block_num]
|
85
109
|
last_block_num = options[:at_block_num].to_i
|
86
110
|
else
|
87
|
-
|
88
|
-
|
111
|
+
url = Meeseeker.default_url(chain)
|
112
|
+
database_api = Meeseeker.database_api_class(chain).new(url: url)
|
113
|
+
last_block_num = redis.get(chain_key_prefix + LAST_BLOCK_NUM_KEY_SUFFIX).to_i + 1
|
89
114
|
|
90
115
|
block_num = catch :dynamic_global_properties do
|
91
116
|
database_api.get_dynamic_global_properties do |dgpo|
|
@@ -116,11 +141,12 @@ module Meeseeker
|
|
116
141
|
end
|
117
142
|
|
118
143
|
begin
|
119
|
-
|
120
|
-
|
144
|
+
url = Meeseeker.default_url(chain)
|
145
|
+
stream_options = {chain: chain, url: url}
|
146
|
+
stream_args = [last_block_num, mode]
|
121
147
|
condenser_api = nil
|
122
148
|
|
123
|
-
|
149
|
+
Radiator::Stream.new(stream_options).tap do |stream|
|
124
150
|
puts "Stream begin: #{stream_options.to_json}; #{options.to_json}"
|
125
151
|
|
126
152
|
# Prior to v0.0.4, we only streamed operations with stream.operations.
|
@@ -129,67 +155,89 @@ module Meeseeker
|
|
129
155
|
# to embed it into op values. This should also reduce streaming
|
130
156
|
# overhead since we no longer stream block_headers inder the hood.
|
131
157
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
yield op, b.transaction_ids[index], n
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
next unless !!Meeseeker.include_virtual
|
142
|
-
|
143
|
-
retries = 0
|
144
|
-
|
145
|
-
# This is where it gets tricky. Virtual ops sometims don't show up
|
146
|
-
# right away, especially if we're streaming on head blocks. In that
|
147
|
-
# situation, we might only need to wait about 1 block. This loop
|
148
|
-
# will likely one execute one iteration, but we have fallback logic
|
149
|
-
# in case there are complications.
|
150
|
-
#
|
151
|
-
# See: https://developers.steem.io/tutorials-recipes/virtual-operations-when-streaming-blockchain-transactions
|
152
|
-
|
153
|
-
loop do
|
154
|
-
condenser_api ||= Steem::CondenserApi.new(url: Meeseeker.node_url)
|
155
|
-
condenser_api.get_ops_in_block(n, true) do |vops|
|
156
|
-
redo if vops.nil?
|
158
|
+
loop do
|
159
|
+
begin
|
160
|
+
stream.blocks(*stream_args) do |b, n, condenser_api|
|
161
|
+
redo if b.nil?
|
157
162
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
# impact overall performance because steem-ruby will batch
|
162
|
-
# when block streams fall behind.
|
163
|
-
|
164
|
-
if retries < MAX_VOP_RETRY
|
165
|
-
retries = retries + 1
|
166
|
-
condenser_api = nil
|
167
|
-
sleep 3 * retries
|
163
|
+
b.transactions.each_with_index do |transaction, index|
|
164
|
+
transaction.operations.each do |op|
|
165
|
+
op_value = op[1].merge(timestamp: b.timestamp)
|
168
166
|
|
169
|
-
|
167
|
+
yield Hashie::Mash.new(type: op[0], value: op_value), b.transaction_ids[index], n
|
170
168
|
end
|
171
|
-
|
172
|
-
puts "Gave up retrying virtual ops lookup on block #{n}"
|
173
|
-
|
174
|
-
break
|
175
169
|
end
|
176
170
|
|
177
|
-
|
178
|
-
|
179
|
-
|
171
|
+
next unless !!Meeseeker.include_virtual
|
172
|
+
|
173
|
+
retries = 0
|
174
|
+
|
175
|
+
# This is where it gets tricky. Virtual ops sometims don't show up
|
176
|
+
# right away, especially if we're streaming on head blocks. In that
|
177
|
+
# situation, we might only need to wait about 1 block. This loop
|
178
|
+
# will likely one execute one iteration, but we have fallback logic
|
179
|
+
# in case there are complications.
|
180
|
+
#
|
181
|
+
# See: https://developers.steem.io/tutorials-recipes/virtual-operations-when-streaming-blockchain-transactions
|
180
182
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
183
|
+
loop do
|
184
|
+
# TODO (HF23) Switch to account_history_api.enum_virtual_ops if supported.
|
185
|
+
url = Meeseeker.default_url(chain)
|
186
|
+
condenser_api ||= Meeseeker.condenser_api_class(chain).new(url: url)
|
187
|
+
condenser_api.get_ops_in_block(n, true) do |vops|
|
188
|
+
if vops.nil?
|
189
|
+
puts "Node returned empty result for get_ops_in_block on block_num: #{n} (rate limiting?). Retrying ..."
|
190
|
+
vops = []
|
191
|
+
end
|
192
|
+
|
193
|
+
if vops.empty? && mode != :head
|
194
|
+
# Usually, we just need to slow down to allow virtual ops to
|
195
|
+
# show up after a short delay. Adding this delay doesn't
|
196
|
+
# impact overall performance because steem-ruby will batch
|
197
|
+
# when block streams fall behind.
|
198
|
+
|
199
|
+
if retries < MAX_VOP_RETRY
|
200
|
+
retries = retries + 1
|
201
|
+
condenser_api = nil
|
202
|
+
sleep Meeseeker::BLOCK_INTERVAL * retries
|
203
|
+
|
204
|
+
redo
|
205
|
+
end
|
206
|
+
|
207
|
+
puts "Gave up retrying virtual ops lookup on block #{n}"
|
208
|
+
|
209
|
+
break
|
210
|
+
end
|
211
|
+
|
212
|
+
if retries > 0
|
213
|
+
puts "Found virtual ops for block #{n} aftere #{retries} retrie(s)"
|
214
|
+
end
|
215
|
+
|
216
|
+
vops.each do |vop|
|
217
|
+
normalized_op = Hashie::Mash.new(
|
218
|
+
type: vop.op[0],
|
219
|
+
value: vop.op[1],
|
220
|
+
timestamp: vop.timestamp
|
221
|
+
)
|
222
|
+
|
223
|
+
yield normalized_op, vop.trx_id, vop.block
|
224
|
+
end
|
225
|
+
end
|
187
226
|
|
188
|
-
|
227
|
+
break
|
189
228
|
end
|
190
229
|
end
|
191
230
|
|
192
231
|
break
|
232
|
+
rescue => e
|
233
|
+
raise e unless e.to_s.include? 'Request Entity Too Large'
|
234
|
+
|
235
|
+
# We need to tell steem-ruby to avoid json-rpc-batch on this
|
236
|
+
# node.
|
237
|
+
|
238
|
+
Meeseeker.block_api_class(chain).const_set 'MAX_RANGE_SIZE', 1
|
239
|
+
sleep Meeseeker::BLOCK_INTERVAL
|
240
|
+
redo
|
193
241
|
end
|
194
242
|
end
|
195
243
|
end
|