meeseeker 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ee0984fc73172da1b1186ecaf6efee7301857ef06daa1b140f16cc732e93e97f
4
- data.tar.gz: 60c2cad0dc9ddd541323dca737f1a6c017f0dc4b458075c5d2512b960133c863
3
+ metadata.gz: ad89ce6d8e55cafd5dcca2f0ddc2455c23ece9dab83c2d5e1a3f0a59df6c6a4a
4
+ data.tar.gz: e709c970eddc2c22c92d6f9482757305b851efc9603ccc725892e1feeb5a966d
5
5
  SHA512:
6
- metadata.gz: d031ecca3a20d7b4c484baa461bbe35a2405c240ee4a2cb0ba45d6342bbba91a884cc55b347fd7db4fcdd987734e6d163bbf29be302127d5cf9674215333b592
7
- data.tar.gz: f4824210cb157c65a80fdb217f9ec30dc55e09dde132ebe62db3896a08f7b50d2b21978ce9721d7b60312fd3c629abb7f2755f235b2f239281039b394578b35e
6
+ metadata.gz: 2a443c55716a8b6c605cb8cd5e72aee8008ba12f5641df0d1dd717e7db2535b4df9895b3d011ca07ab4c80df8164806243ff41eb82de68aa1fdfd0ce76962356
7
+ data.tar.gz: 53d277d9d1a8c848691bee1c4ecf6406ca61987a3b5010c657bad7485b87a25fd579fef8d7fc06108c2e06b91b50b97ff4b94e70414b2d2afefb1e2c7390b5dd
data/README.md CHANGED
@@ -288,13 +288,52 @@ Or to get all ops for a particular transaction:
288
288
  redis-cli --scan --pattern 'steem:*:31ecb9c85e9eabd7ca2460fdb4f3ce4a7ca6ec32:*'
289
289
  ```
290
290
 
291
- ---
291
+ ### Steem Engine Support
292
292
 
293
- <center>
294
- <img src="https://i.imgur.com/Y3Sa2GW.jpg" />
295
- </center>
293
+ As of `v0.0.6`, meeseeker can also follow the Steem Engine side-chain. This is optional and requires a separate process.
296
294
 
297
- See some of my previous Ruby How To posts in: [#radiator](https://steemit.com/created/radiator) [#ruby](https://steemit.com/created/ruby)
295
+ To sync Steem Engine to your local redis source (also defaults to `redis://127.0.0.1:6379/0`):
296
+
297
+ ```bash
298
+ meeseeker sync steem_engine
299
+ ```
300
+
301
+ When running `meeseeker sync steem_engine`, the following channels are available:
302
+
303
+ * `steem_engine:block`
304
+ * `steem_engine:transaction`
305
+ * `steem_engine:market:buy`
306
+ * `steem_engine:market:cancel`
307
+ * `steem_engine:market:sell`
308
+ * `steem_engine:sscstore:buy`
309
+ * `steem_engine:steempegged:buy`
310
+ * `steem_engine:steempegged:removeWithdrawal`
311
+ * `steem_engine:steempegged:withdraw`
312
+ * `steem_engine:tokens:create`
313
+ * `steem_engine:tokens:issue`
314
+ * `steem_engine:tokens:transfer`
315
+ * `steem_engine:tokens:updateMetadata`
316
+ * `steem_engine:tokens:updateUrl`
317
+
318
+ The above "channel/action" patterns are the ones that are known that the time of writing. In addition, if a new contract is added or updated, meeseeker will automatically publish to these corresponding channels as they appear, without needing to update or even restart meeseeker.
319
+
320
+ See main section on [Using `SUBSCRIBE`](#using-subscribe).
321
+
322
+ Once your SteemEngine sync has started, you can begin doing queries against redis, for example, in the `redis-cli`:
323
+
324
+ ```bash
325
+ redis-cli --scan --pattern 'steem_engine:*:tokens:transfer'
326
+ ```
327
+
328
+ This returns the keys, for example:
329
+
330
+ ```
331
+ steem_engine:18000:d414373db84e6a642f289641ea1433fda22b8a4d:0:tokens:transfer
332
+ steem_engine:18004:c9e06c8449d2d04b4a0a31ec7b80d2f62009a5f0:0:tokens:transfer
333
+ steem_engine:17994:faf097391760ad896b19d5854e2822f62dee284b:0:tokens:transfer
334
+ ```
335
+
336
+ See main section on [Using `SCAN`](#using-scan).
298
337
 
299
338
  ### Docker
300
339
 
@@ -316,6 +355,14 @@ docker run \
316
355
 
317
356
  Also see: https://hub.docker.com/r/inertia/meeseeker/
318
357
 
358
+ ---
359
+
360
+ <center>
361
+ <img src="https://i.imgur.com/Y3Sa2GW.jpg" />
362
+ </center>
363
+
364
+ See some of my previous Ruby How To posts in: [#radiator](https://steemit.com/created/radiator) [#ruby](https://steemit.com/created/ruby)
365
+
319
366
  ## Get in touch!
320
367
 
321
368
  If you're using Radiator, I'd love to hear from you. Drop me a line and tell me what you think! I'm @inertia on STEEM.
data/Rakefile CHANGED
@@ -31,6 +31,16 @@ task :push do
31
31
  exec "gem push meeseeker-#{Meeseeker::VERSION}.gem"
32
32
  end
33
33
 
34
+ desc 'Build a new version of the meeseeker docker image.'
35
+ task :docker_build do
36
+ exec 'docker build -t inertia/meeseeker:latest .'
37
+ end
38
+
39
+ desc 'Publish the current version of the meeseeker docker image.'
40
+ task :docker_push do
41
+ exec 'docker push inertia/meeseeker:latest'
42
+ end
43
+
34
44
  task :check_schema do
35
45
  begin
36
46
  abort 'Unable to ping redis source.' unless Meeseeker.redis.ping == 'PONG'
@@ -41,8 +51,17 @@ task :check_schema do
41
51
  end
42
52
  end
43
53
 
44
- task(:sync, [:at_block_num] => [:check_schema]) do |t, args|
45
- job = Meeseeker::BlockFollowerJob.new
54
+ task(:sync, [:chain, :at_block_num] => [:check_schema]) do |t, args|
55
+ chain = (args[:chain] || 'steem').to_sym
56
+
57
+ job = case chain
58
+ when :steem
59
+ Meeseeker::BlockFollowerJob.new
60
+ when :steem_engine
61
+ Meeseeker::SteemEngine::FollowerJob.new
62
+ else; abort("Unknown chain: #{chain}")
63
+ end
64
+
46
65
  job.perform(at_block_num: args[:at_block_num])
47
66
  end
48
67
 
@@ -54,11 +73,13 @@ namespace :witness do
54
73
  end
55
74
  end
56
75
 
57
- task(:find, [:what, :key] => [:check_schema]) do |t, args|
76
+ task(:find, [:what, :key, :chain] => [:check_schema]) do |t, args|
77
+ chain = (args[:chain] || 'steem').downcase.to_sym
58
78
  redis = Meeseeker.redis
79
+
59
80
  match = case args[:what].downcase.to_sym
60
- when :block then "steem:#{args[:key]}:*"
61
- when :trx then "steem:*:#{args[:key]}:*"
81
+ when :block then "#{chain}:#{args[:key]}:*"
82
+ when :trx then "#{chain}:*:#{args[:key]}:*"
62
83
  else; abort "Unknown lookup using #{args}"
63
84
  end
64
85
 
@@ -74,6 +95,7 @@ end
74
95
  task reset: [:check_schema] do
75
96
  print 'Dropping keys ...'
76
97
  keys = Meeseeker.redis.keys('steem:*')
98
+ keys += Meeseeker.redis.keys('steem_engine:*')
77
99
 
78
100
  if keys.any?
79
101
  print " found #{keys.size} keys ..."
@@ -221,6 +243,119 @@ namespace :verify do
221
243
  end
222
244
  end
223
245
 
246
+ desc 'Verifies Steem Engine transactions land where they should.'
247
+ task :steem_engine_block_org, [:max_blocks] do |t, args|
248
+ max_blocks = args[:max_blocks]
249
+ node_url = ENV.fetch('MEESEEKER_STEEM_ENGINE_NODE_URL', 'https://api.steem-engine.com/rpc')
250
+ agent = Meeseeker::SteemEngine::Agent.new(url: node_url)
251
+ until_block_num = if !!max_blocks
252
+ agent.latest_block_info['blockNumber']
253
+ end
254
+
255
+ Thread.new do
256
+ job = Meeseeker::SteemEngine::FollowerJob.new
257
+
258
+ loop do
259
+ begin
260
+ at_block_num = agent.latest_block_info["blockNumber"] - max_blocks.to_i
261
+ at_block_num = [at_block_num, 1].max
262
+ job.perform(at_block_num: at_block_num, until_block_num: until_block_num)
263
+ rescue => e
264
+ puts e.inspect
265
+ sleep 5
266
+ end
267
+
268
+ break # success
269
+ end
270
+
271
+ puts 'Background sync finished ...'
272
+ end
273
+
274
+ begin
275
+ block_channel = 'steem_engine:block'
276
+ redis_url = ENV.fetch('MEESEEKER_REDIS_URL', 'redis://127.0.0.1:6379/0')
277
+ subscription = Redis.new(url: redis_url)
278
+ ctx = Redis.new(url: redis_url)
279
+ timeout = (max_blocks).to_i * 3
280
+
281
+ subscribe_mode, subscribe_args = if timeout > 0
282
+ [:subscribe_with_timeout, [timeout, [block_channel]]]
283
+ else
284
+ [:subscribe, [[block_channel]]]
285
+ end
286
+
287
+ subscription.send(subscribe_mode, *subscribe_args) do |on|
288
+ on.subscribe do |channel, subscriptions|
289
+ puts "Subscribed to ##{channel} (subscriptions: #{subscriptions})"
290
+ end
291
+
292
+ on.message do |channel, message|
293
+ payload = JSON[message]
294
+ block_num = payload['block_num']
295
+ next_block_num = block_num + 1
296
+
297
+ if !!max_blocks
298
+ if block_num >= until_block_num
299
+ # We're done trailing blocks. Typically, this is used by unit
300
+ # tests so the test can halt.
301
+
302
+ subscription.unsubscribe
303
+ next
304
+ end
305
+ end
306
+
307
+ while ctx.keys("steem_engine:#{next_block_num}:*").size == 0
308
+ # This ensures at least the next block has been indexed before
309
+ # proceeding.
310
+
311
+ puts "Waiting for block: #{next_block_num} ..."
312
+ sleep 6
313
+ end
314
+
315
+ # In theory, we should have all the keys using this pattern.
316
+ keys = ctx.keys("steem_engine:#{block_num}:*")
317
+
318
+ # If we have all the keys, we should also have all transaction ids.
319
+ expected_ids = keys.map { |k| k.split(':')[2] }.uniq
320
+ actual_ids = nil
321
+
322
+ agent.block(block_num).tap do |block|
323
+ raise 'Got empty block result.' if block.nil?
324
+
325
+ actual_ids = block['transactions'].map{|trx| trx['transactionId'].split('-').first}.uniq
326
+ end
327
+
328
+ # We do an intersection to make sure there's no difference between
329
+ # the two copies, regardless of order, as opposed to just checking that
330
+ # the lengths match.
331
+
332
+ (actual_ids & expected_ids).tap do |intersection|
333
+ all_sizes = [intersection.size, expected_ids.size, actual_ids.size]
334
+ puts 'intersection: %d; expected: %d; actual: %d' % all_sizes
335
+
336
+ if all_sizes.min != all_sizes.max
337
+ puts "Expected transaction ids:"
338
+ puts expected_ids
339
+ puts "Actual transaction ids:"
340
+ puts actual_ids
341
+
342
+ puts "actual_ids minus expected:"
343
+ puts actual_ids - expected_ids
344
+ puts "expected_ids minus actual:"
345
+ puts expected_ids - actual_ids
346
+
347
+ exit(-1)
348
+ end
349
+ end
350
+ end
351
+
352
+ on.unsubscribe do |channel, subscriptions|
353
+ puts "Unsubscribed from ##{channel} (subscriptions: #{subscriptions})"
354
+ end
355
+ end
356
+ end
357
+ end
358
+
224
359
  namespace :witness do
225
360
  desc 'Verifies witnessses in the schedule produced a block.'
226
361
  task :schedule, [:max_blocks] do |t, args|
data/bin/meeseeker CHANGED
@@ -23,7 +23,7 @@ when 'sync', 'witness:schedule'
23
23
  max_backoff = 30
24
24
 
25
25
  loop do; begin
26
- Rake::Task[ARGV[0]].invoke(ARGV[1])
26
+ Rake::Task[ARGV[0]].invoke(*ARGV[1..-1])
27
27
  rescue => e
28
28
  puts "Error: #{e.inspect}"
29
29
  backoff = [backoff, max_backoff].min
@@ -35,7 +35,7 @@ when 'find' then Rake::Task['find'].invoke(*ARGV[1..-1])
35
35
  when 'reset' then Rake::Task['reset'].invoke
36
36
  else
37
37
  puts "\nBegin/resume sync:"
38
- puts "\t#{filename} sync\n\n"
38
+ puts "\t#{filename} sync [chain] [block_num]\n\n"
39
39
  puts "Publish witness schedule:"
40
40
  puts "\t#{filename} witness:schedule\n\n"
41
41
  puts "Start in the ruby console:"
data/lib/meeseeker.rb CHANGED
@@ -4,13 +4,17 @@ require 'steem'
4
4
  require 'meeseeker/version'
5
5
  require 'meeseeker/block_follower_job'
6
6
  require 'meeseeker/witness_schedule_job'
7
+ require 'meeseeker/steem_engine/agent'
8
+ require 'meeseeker/steem_engine/follower_job'
7
9
 
8
10
  module Meeseeker
9
11
  LAST_BLOCK_NUM_KEY = 'steem:meeseeker:last_block_num'
12
+ LAST_STEEM_ENGINE_BLOCK_NUM_KEY = 'steem:meeseeker:last_steem_engine_block_num'
10
13
  BLOCKS_PER_DAY = 28800
11
14
  VIRTUAL_TRX_ID = '0000000000000000000000000000000000000000'
12
15
  @redis = Redis.new(url: ENV.fetch('MEESEEKER_REDIS_URL', 'redis://127.0.0.1:6379/0'))
13
16
  @node_url = ENV.fetch('MEESEEKER_NODE_URL', 'https://api.steemit.com')
17
+ @steem_engine_node_url = ENV.fetch('MEESEEKER_STEEM_ENGINE_NODE_URL', 'https://api.steem-engine.com/rpc')
14
18
  @stream_mode = ENV.fetch('MEESEEKER_STREAM_MODE', 'head').downcase.to_sym
15
19
  @include_virtual = ENV.fetch('MEESEEKER_INCLUDE_VIRTUAL', 'true').downcase == 'true'
16
20
  @include_block_header = ENV.fetch('MEESEEKER_INCLUDE_BLOCK_HEADER', 'true').downcase == 'true'
@@ -19,6 +23,6 @@ module Meeseeker
19
23
 
20
24
  extend self
21
25
 
22
- attr_accessor :redis, :node_url, :expire_keys, :stream_mode, :include_virtual,
23
- :include_block_header, :publish_op_custom_id
26
+ attr_accessor :redis, :node_url, :steem_engine_node_url, :expire_keys,
27
+ :stream_mode, :include_virtual, :include_block_header, :publish_op_custom_id
24
28
  end
@@ -0,0 +1,60 @@
1
+ require 'mechanize'
2
+
3
+ module Meeseeker::SteemEngine
4
+ class Agent < Mechanize
5
+ POST_HEADERS = {
6
+ 'Content-Type' => 'application/json; charset=utf-8',
7
+ 'User-Agent' => Meeseeker::AGENT_ID
8
+ }
9
+
10
+ def initialize(options = {})
11
+ super
12
+
13
+ self.user_agent = Meeseeker::AGENT_ID
14
+ self.max_history = 0
15
+ self.default_encoding = 'UTF-8'
16
+
17
+ @node_url = options[:url] || Meeseeker::steem_engine_node_url
18
+ end
19
+
20
+ def blockchain_uri
21
+ @blockchain_uri ||= URI.parse(@node_url + '/blockchain')
22
+ end
23
+
24
+ def blockchain_http_post
25
+ @http_post ||= Net::HTTP::Post.new(blockchain_uri.request_uri, POST_HEADERS)
26
+ end
27
+
28
+ def latest_block_info
29
+ request_body = {
30
+ jsonrpc: "2.0",
31
+ method: :getLatestBlockInfo,
32
+ id: rpc_id
33
+ }.to_json
34
+
35
+ response = request_with_entity :post, blockchain_uri, request_body, POST_HEADERS
36
+
37
+ JSON[response.body]["result"]
38
+ end
39
+
40
+ def block(block_num)
41
+ request_body = {
42
+ jsonrpc: "2.0",
43
+ method: :getBlockInfo,
44
+ params: {
45
+ blockNumber: block_num
46
+ },
47
+ id: rpc_id
48
+ }.to_json
49
+
50
+ response = request_with_entity :post, blockchain_uri, request_body, POST_HEADERS
51
+
52
+ JSON[response.body]["result"]
53
+ end
54
+ private
55
+ def rpc_id
56
+ @rpc_id ||= 0
57
+ @rpc_id = @rpc_id + 1
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,107 @@
1
+ module Meeseeker::SteemEngine
2
+ class FollowerJob
3
+ def perform(options = {})
4
+ redis = Meeseeker.redis
5
+ last_key_prefix = nil
6
+ trx_index = 0
7
+ current_block_num = nil
8
+ block_transactions = []
9
+
10
+ stream_transactions(options) do |transaction, block|
11
+ begin
12
+ trx_id = transaction['transactionId'].split('-').first
13
+ block_num = block['blockNumber']
14
+ current_key_prefix = "steem_engine:#{block_num}:#{trx_id}"
15
+ contract = transaction['contract']
16
+ action = transaction['action']
17
+
18
+ if current_key_prefix == last_key_prefix
19
+ trx_index += 1
20
+ else
21
+ if !!last_key_prefix
22
+ n, b, t = last_key_prefix.split(':')
23
+ transaction_payload = {
24
+ block_num: b.to_i,
25
+ transaction_id: t,
26
+ transaction_num: block_transactions.size
27
+ }
28
+
29
+ block_transactions << trx_id
30
+ redis.publish('steem_engine:transaction', transaction_payload.to_json)
31
+ end
32
+
33
+ last_key_prefix = "steem_engine:#{block_num}:#{trx_id}"
34
+ trx_index = 0
35
+ end
36
+
37
+ key = "#{current_key_prefix}:#{trx_index}:#{contract}:#{action}"
38
+ puts key
39
+ end
40
+
41
+ redis.set(key, transaction.to_json)
42
+ redis.expire(key, Meeseeker.expire_keys) unless Meeseeker.expire_keys == -1
43
+
44
+ if current_block_num != block_num
45
+ block_transactions = []
46
+ block_payload = {
47
+ block_num: block_num
48
+ }
49
+
50
+ redis.set(Meeseeker::LAST_STEEM_ENGINE_BLOCK_NUM_KEY, block_num)
51
+ redis.publish('steem_engine:block', block_payload.to_json)
52
+ current_block_num = block_num
53
+ end
54
+
55
+ redis.publish("steem_engine:#{contract}:#{action}", {key: key}.to_json)
56
+ end
57
+ end
58
+ private
59
+ def stream_transactions(options = {}, &block)
60
+ redis = Meeseeker.redis
61
+ last_block_num = nil
62
+ agent = Agent.new
63
+
64
+ if !!options[:at_block_num]
65
+ last_block_num = options[:at_block_num].to_i
66
+ else
67
+ last_block_num = redis.get(Meeseeker::LAST_STEEM_ENGINE_BLOCK_NUM_KEY).to_i + 1
68
+ block_info = agent.latest_block_info
69
+ block_num = block_info['blockNumber']
70
+
71
+ last_block = agent.block(last_block_num)
72
+ last_block_timestamp = Time.parse(last_block['timestamp'] + 'Z')
73
+
74
+ if Meeseeker.expire_keys == -1
75
+ last_block_num = [last_block_num, block_num].max
76
+
77
+ puts "Sync Steem Engine from: #{last_block_num}"
78
+ elsif Time.now.utc - last_block_timestamp > Meeseeker.expire_keys
79
+ last_block_num = block_num + 1
80
+
81
+ puts 'Starting new Steem Engine sync.'
82
+ else
83
+ puts "Resuming from Steem Engine block #{last_block_num} ..."
84
+ end
85
+ end
86
+
87
+ block_num = last_block_num
88
+
89
+ loop do
90
+ block = agent.block(block_num)
91
+
92
+ if block.nil?
93
+ sleep 3 # sleep for one mainnet block interval
94
+ redo
95
+ end
96
+
97
+ transactions = block['transactions']
98
+
99
+ transactions.each do |transaction|
100
+ yield transaction.merge(timestamp: block['timestamp']), block
101
+ end
102
+
103
+ block_num = block_num + 1
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,3 +1,4 @@
1
1
  module Meeseeker
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
+ AGENT_ID = "meeseeker/#{VERSION}"
3
4
  end
data/meeseeker.gemspec CHANGED
@@ -26,6 +26,7 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency 'minitest-proveit', '~> 1.0', '>= 1.0.0'
27
27
  s.add_development_dependency 'simplecov', '~> 0.15', '>= 0.15.1'
28
28
  s.add_development_dependency 'pry', '~> 0.11', '>= 0.11.3'
29
+ s.add_development_dependency 'irb', '~> 1.0', '>= 1.0.0'
29
30
 
30
31
  s.add_dependency 'redis', '~> 4.1', '>= 4.1.0'
31
32
  s.add_dependency 'steem-mechanize', '~> 0.0', '>= 0.0.5'
@@ -22,6 +22,7 @@ module Meeseeker
22
22
  max_blocks = 30 # must be at least 15 to get past irreversible
23
23
 
24
24
  assert Rake::Task['verify:block_org'].invoke(max_blocks)
25
+ assert Rake::Task['verify:steem_engine_block_org'].invoke(max_blocks)
25
26
  assert Rake::Task['verify:witness:schedule'].invoke(max_blocks)
26
27
 
27
28
  Rake::Task['reset'].invoke
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: meeseeker
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anthony Martin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-11 00:00:00.000000000 Z
11
+ date: 2019-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -130,6 +130,26 @@ dependencies:
130
130
  - - ">="
131
131
  - !ruby/object:Gem::Version
132
132
  version: 0.11.3
133
+ - !ruby/object:Gem::Dependency
134
+ name: irb
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - "~>"
138
+ - !ruby/object:Gem::Version
139
+ version: '1.0'
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: 1.0.0
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - "~>"
148
+ - !ruby/object:Gem::Version
149
+ version: '1.0'
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.0.0
133
153
  - !ruby/object:Gem::Dependency
134
154
  name: redis
135
155
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +227,8 @@ files:
207
227
  - bin/meeseeker
208
228
  - lib/meeseeker.rb
209
229
  - lib/meeseeker/block_follower_job.rb
230
+ - lib/meeseeker/steem_engine/agent.rb
231
+ - lib/meeseeker/steem_engine/follower_job.rb
210
232
  - lib/meeseeker/version.rb
211
233
  - lib/meeseeker/witness_schedule_job.rb
212
234
  - meeseeker.gemspec