meeseeker 0.0.5 → 0.0.6

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 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