zold 0.14.52 → 0.14.53

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: 3f2a2f29076ab430c597f9d07c7a72b7f10a378e3c69b9ff755b04f644accdb9
4
- data.tar.gz: 5bab77f75e6b20cf1e80cb1f8bea44353ec0060e523c0c2c08614334d728c22b
3
+ metadata.gz: 12bf3dea4ff2630f3530269b7c5db959ad30cec8ab6346ba3336570c4a61822d
4
+ data.tar.gz: 1cb07c0e0ce665ededffc57a221ead9a235d99ba9eb391a8a75b1af6434bf093
5
5
  SHA512:
6
- metadata.gz: 7aec846b0b6443e195e989a414254fe80bca5e810b19000e60e574ba4721b64cb3ac820f85811c96b2a23db45c1c64291d0ae63e1eb619a30d5f27406354a432
7
- data.tar.gz: 41d673a22f703fc01bdc857f61a7c82d3fa34059e1a05c2cd759532d65606df723e2810bff55ab44d6f2ef9f2c0914ec9c713ea557924b1244b1f6575c17ad3f
6
+ metadata.gz: 4788bfa23317ab20259ef9fae9266556d402caf07de529d8358d9541f61dca9f42b2921b6d15535e8b11583c4f79c130aae3fa82cda5cd8fd494aeb645f8fd8d
7
+ data.tar.gz: 2a929f263c3a0a2fa91e6cddc9436b273781c58f121753ad28191f4fa140b5df7b9f07bda75e84dcc448e6d30633695895906fac47089be453246b83f3665e83
@@ -28,7 +28,6 @@ require_relative 'args'
28
28
  require_relative '../log'
29
29
  require_relative '../patch'
30
30
  require_relative '../wallet'
31
- require_relative '../atomic_file'
32
31
 
33
32
  # DIFF command.
34
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -68,7 +67,7 @@ Available options:"
68
67
  patch.join(Wallet.new(c[:path]))
69
68
  end
70
69
  before = @wallets.find(id) do |wallet|
71
- AtomicFile.new(wallet.path).read
70
+ File.read(wallet.path)
72
71
  end
73
72
  after = ''
74
73
  Tempfile.open(['', Wallet::EXTENSION]) do |f|
@@ -135,6 +135,12 @@ module Zold
135
135
  'Don\'t run the metronome',
136
136
  required: true,
137
137
  default: false
138
+ o.bool '--disable-push',
139
+ 'Prohibit all PUSH requests',
140
+ default: false
141
+ o.bool '--disable-fetch',
142
+ 'Prohibit all FETCH requests',
143
+ default: false
138
144
  o.string '--alias',
139
145
  'The alias of the node (default: host:port)',
140
146
  require: false
@@ -160,6 +166,8 @@ module Zold
160
166
  Front.set(:protocol, Zold::PROTOCOL)
161
167
  Front.set(:logging, @log.debug?)
162
168
  Front.set(:halt, opts['halt-code'])
169
+ Front.set(:disable_push, opts['disable-push'])
170
+ Front.set(:disable_fetch, opts['disable-fetch'])
163
171
  Front.set(:home, opts['home'])
164
172
  @log.info("Time: #{Time.now.utc.iso8601}")
165
173
  @log.info("Home directory: #{opts['home']}")
@@ -198,7 +206,7 @@ module Zold
198
206
  Front.set(:port, opts['bind-port'])
199
207
  Front.set(:reboot, !opts['never-reboot'])
200
208
  node_alias = opts[:alias] || address
201
- unless node_alias.eql?(address) || node_alias =~ /^[a-z0-9]{4,16}$/
209
+ unless node_alias.eql?(address) || node_alias =~ /^[A-Za-z0-9]{4,16}$/
202
210
  raise "Alias should be a 4 to 16 char long alphanumeric string: #{node_alias}"
203
211
  end
204
212
  Front.set(:node_alias, node_alias)
@@ -375,7 +383,7 @@ module Zold
375
383
  end
376
384
 
377
385
  def info(msg)
378
- @log.debug(msg)
386
+ @log.debug('WEBRICK ' + msg)
379
387
  end
380
388
 
381
389
  def debug(msg)
@@ -383,11 +391,11 @@ module Zold
383
391
  end
384
392
 
385
393
  def error(msg)
386
- @log.error(msg)
394
+ @log.error('WEBRICK ' + msg)
387
395
  end
388
396
 
389
397
  def fatal(msg)
390
- @log.error(msg)
398
+ @log.error('WEBRICK ' + msg)
391
399
  end
392
400
 
393
401
  def debug?
@@ -31,7 +31,6 @@ require_relative '../log'
31
31
  require_relative '../id'
32
32
  require_relative '../http'
33
33
  require_relative '../json_page'
34
- require_relative '../atomic_file'
35
34
 
36
35
  # PUSH command.
37
36
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -91,7 +90,7 @@ total score for #{id} is #{total}")
91
90
  start = Time.now
92
91
  content = @wallets.find(id) do |wallet|
93
92
  raise "The wallet #{id} is absent" unless wallet.exists?
94
- AtomicFile.new(wallet.path).read
93
+ File.read(wallet.path)
95
94
  end
96
95
  uri = "/wallet/#{id}"
97
96
  response = r.http(uri).put(content)
data/lib/zold/copies.rb CHANGED
@@ -23,7 +23,6 @@
23
23
  require 'time'
24
24
  require 'csv'
25
25
  require 'backtrace'
26
- require_relative 'atomic_file'
27
26
  require_relative 'log'
28
27
  require_relative 'size'
29
28
  require_relative 'wallet'
@@ -104,7 +103,7 @@ module Zold
104
103
  list = load
105
104
  target = list.find do |s|
106
105
  f = File.join(@dir, "#{s[:name]}#{Copies::EXT}")
107
- File.exist?(f) && AtomicFile.new(f).read == content
106
+ File.exist?(f) && File.read(f) == content
108
107
  end
109
108
  if target.nil?
110
109
  max = Dir.new(@dir)
@@ -113,7 +112,7 @@ module Zold
113
112
  .max
114
113
  max = 0 if max.nil?
115
114
  name = (max + 1).to_s
116
- AtomicFile.new(File.join(@dir, "#{name}#{Copies::EXT}")).write(content)
115
+ File.write(File.join(@dir, "#{name}#{Copies::EXT}"), content)
117
116
  else
118
117
  name = target[:name]
119
118
  end
@@ -161,7 +160,8 @@ module Zold
161
160
  private
162
161
 
163
162
  def save(list)
164
- AtomicFile.new(file).write(
163
+ File.write(
164
+ file,
165
165
  list.map do |r|
166
166
  [
167
167
  r[:name], r[:host],
data/lib/zold/head.rb CHANGED
@@ -39,7 +39,7 @@ module Zold
39
39
 
40
40
  def fetch
41
41
  raise "Wallet file '#{@file}' is absent" unless File.exist?(@file)
42
- lines = AtomicFile.new(@file).read.split(/\n/)
42
+ lines = File.read(@file).split(/\n/)
43
43
  raise "Not enough lines in #{@file}, just #{lines.count}" if lines.count < 4
44
44
  lines.take(4)
45
45
  end
data/lib/zold/key.rb CHANGED
@@ -24,7 +24,6 @@ gem 'openssl'
24
24
  require 'openssl'
25
25
  require 'base64'
26
26
  require 'tempfile'
27
- require_relative 'atomic_file'
28
27
 
29
28
  # The RSA key (either private or public).
30
29
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -38,7 +37,7 @@ module Zold
38
37
  unless file.nil?
39
38
  path = File.expand_path(file)
40
39
  raise "Can't find RSA key at #{file} (#{path})" unless File.exist?(path)
41
- return AtomicFile.new(path).read
40
+ return File.read(path)
42
41
  end
43
42
  unless text.nil?
44
43
  return text if text.start_with?('-----')
@@ -97,7 +97,7 @@ module Zold
97
97
  def push(id, body)
98
98
  raise "Queue is too long (#{queue.count} wallets), try again later" if queue.count > AsyncEntrance::MAX_QUEUE
99
99
  @mutex.synchronize do
100
- AtomicFile.new(File.join(@dir, id.to_s)).write(body)
100
+ File.write(File.join(@dir, id.to_s), body)
101
101
  end
102
102
  [id]
103
103
  end
@@ -27,7 +27,6 @@ require_relative '../log'
27
27
  require_relative '../score'
28
28
  require_relative '../age'
29
29
  require_relative '../verbose_thread'
30
- require_relative '../atomic_file'
31
30
 
32
31
  # The farm of scores.
33
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -222,7 +221,8 @@ module Zold
222
221
  scores = load + list
223
222
  period = 24 * 60 * 60 / [threads, 1].max
224
223
  @mutex.synchronize do
225
- AtomicFile.new(@cache).write(
224
+ File.write(
225
+ @cache,
226
226
  scores.select(&:valid?)
227
227
  .reject(&:expired?)
228
228
  .sort_by(&:value)
@@ -239,7 +239,7 @@ module Zold
239
239
  def load
240
240
  @mutex.synchronize do
241
241
  if File.exist?(@cache)
242
- AtomicFile.new(@cache).read.split(/\n/)
242
+ File.read(@cache).split(/\n/)
243
243
  .map { |t| parse_score_line(t) }
244
244
  .reject(&:zero?)
245
245
  else
@@ -35,11 +35,11 @@ require 'backtrace'
35
35
  require_relative '../version'
36
36
  require_relative '../size'
37
37
  require_relative '../wallet'
38
+ require_relative '../age'
38
39
  require_relative '../copies'
39
40
  require_relative '../log'
40
41
  require_relative '../id'
41
42
  require_relative '../http'
42
- require_relative '../atomic_file'
43
43
 
44
44
  # The web front of the node.
45
45
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -56,7 +56,7 @@ module Zold
56
56
  set :start, Time.now
57
57
  set :lock, false
58
58
  set :show_exceptions, false
59
- set :server, 'webrick'
59
+ set :server, :puma
60
60
  set :log, nil? # to be injected at node.rb
61
61
  set :trace, nil? # to be injected at node.rb
62
62
  set :halt, '' # to be injected at node.rb
@@ -110,17 +110,19 @@ while #{settings.address} is in '#{settings.network}'"
110
110
  cmd = Remote.new(remotes: settings.remotes, log: settings.log)
111
111
  cmd.run(['remote', 'add', s.host, s.port.to_s, "--network=#{settings.network}"])
112
112
  end
113
+ @start = Time.now
113
114
  end
114
115
 
115
116
  # @todo #357:30min Test that the headers are being set correctly.
116
117
  # Currently there are no tests at all that would verify the headers.
117
118
  after do
118
119
  headers['Cache-Control'] = 'no-cache'
119
- headers['Connection'] = 'close'
120
120
  headers['X-Zold-Version'] = settings.version
121
121
  headers[Http::PROTOCOL_HEADER] = settings.protocol.to_s
122
122
  headers['Access-Control-Allow-Origin'] = '*'
123
123
  headers[Http::SCORE_HEADER] = score.reduced(16).to_s
124
+ headers['X-Zold-Thread'] = Thread.current.name
125
+ headers['X-Zold-Milliseconds'] = ((Time.now - @start) * 1000).round.to_s
124
126
  end
125
127
 
126
128
  get '/robots.txt' do
@@ -192,6 +194,7 @@ while #{settings.address} is in '#{settings.network}'"
192
194
  end
193
195
 
194
196
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})} do
197
+ error 404 if settings.disable_fetch
195
198
  id = Id.new(params[:id])
196
199
  settings.wallets.find(id) do |wallet|
197
200
  error 404 unless wallet.exists?
@@ -208,12 +211,13 @@ while #{settings.address} is in '#{settings.network}'"
208
211
  digest: wallet.digest,
209
212
  copies: Copies.new(File.join(settings.copies, id)).all.count,
210
213
  balance: wallet.balance.to_i,
211
- body: AtomicFile.new(wallet.path).read
214
+ body: File.new(wallet.path).read
212
215
  )
213
216
  end
214
217
  end
215
218
 
216
219
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16}).json} do
220
+ error 404 if settings.disable_fetch
217
221
  id = Id.new(params[:id])
218
222
  settings.wallets.find(id) do |wallet|
219
223
  error 404 unless wallet.exists?
@@ -235,6 +239,7 @@ while #{settings.address} is in '#{settings.network}'"
235
239
  end
236
240
 
237
241
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/balance} do
242
+ error 404 if settings.disable_fetch
238
243
  id = Id.new(params[:id])
239
244
  settings.wallets.find(id) do |wallet|
240
245
  error 404 unless wallet.exists?
@@ -244,6 +249,7 @@ while #{settings.address} is in '#{settings.network}'"
244
249
  end
245
250
 
246
251
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/key} do
252
+ error 404 if settings.disable_fetch
247
253
  id = Id.new(params[:id])
248
254
  settings.wallets.find(id) do |wallet|
249
255
  error 404 unless wallet.exists?
@@ -253,6 +259,7 @@ while #{settings.address} is in '#{settings.network}'"
253
259
  end
254
260
 
255
261
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/mtime} do
262
+ error 404 if settings.disable_fetch
256
263
  id = Id.new(params[:id])
257
264
  settings.wallets.find(id) do |wallet|
258
265
  error 404 unless wallet.exists?
@@ -262,6 +269,7 @@ while #{settings.address} is in '#{settings.network}'"
262
269
  end
263
270
 
264
271
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/digest} do
272
+ error 404 if settings.disable_fetch
265
273
  id = Id.new(params[:id])
266
274
  settings.wallets.find(id) do |wallet|
267
275
  error 404 unless wallet.exists?
@@ -271,6 +279,7 @@ while #{settings.address} is in '#{settings.network}'"
271
279
  end
272
280
 
273
281
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.txt} do
282
+ error 404 if settings.disable_fetch
274
283
  id = Id.new(params[:id])
275
284
  settings.wallets.find(id) do |wallet|
276
285
  error 404 unless wallet.exists?
@@ -284,25 +293,27 @@ while #{settings.address} is in '#{settings.network}'"
284
293
  wallet.txns.map(&:to_text).join("\n"),
285
294
  '',
286
295
  '--',
287
- "Balance: #{wallet.balance.to_zld} ZLD (#{wallet.balance.to_i} zents)",
296
+ "Balance: #{wallet.balance.to_zld(8)} ZLD (#{wallet.balance.to_i} zents)",
288
297
  "Transactions: #{wallet.txns.count}",
289
298
  "File size: #{File.size(wallet.path)} bytes (#{Copies.new(File.join(settings.copies, id)).all.count} copies)",
290
- "Modified: #{wallet.mtime.utc.iso8601}",
299
+ "Modified: #{wallet.mtime.utc.iso8601} (#{Age.new(wallet.mtime.utc.iso8601)} ago)",
291
300
  "Digest: #{wallet.digest}"
292
301
  ].join("\n")
293
302
  end
294
303
  end
295
304
 
296
305
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.bin} do
306
+ error 404 if settings.disable_fetch
297
307
  id = Id.new(params[:id])
298
308
  settings.wallets.find(id) do |wallet|
299
309
  error 404 unless wallet.exists?
300
310
  content_type 'text/plain'
301
- AtomicFile.new(wallet.path).read
311
+ File.read(wallet.path)
302
312
  end
303
313
  end
304
314
 
305
315
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/copies} do
316
+ error 404 if settings.disable_fetch
306
317
  id = Id.new(params[:id])
307
318
  settings.wallets.find(id) do |wallet|
308
319
  error 404 unless wallet.exists?
@@ -321,6 +332,7 @@ while #{settings.address} is in '#{settings.network}'"
321
332
  end
322
333
 
323
334
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/copy/(?<name>[0-9]+)} do
335
+ error 404 if settings.disable_fetch
324
336
  id = Id.new(params[:id])
325
337
  name = params[:name]
326
338
  settings.wallets.find(id) do |wallet|
@@ -333,6 +345,7 @@ while #{settings.address} is in '#{settings.network}'"
333
345
  end
334
346
 
335
347
  put %r{/wallet/(?<id>[A-Fa-f0-9]{16})/?} do
348
+ error 404 if settings.disable_push
336
349
  request.body.rewind
337
350
  modified = settings.entrance.push(Id.new(params[:id]), request.body.read.to_s)
338
351
  if modified.empty?
@@ -368,6 +381,16 @@ while #{settings.address} is in '#{settings.network}'"
368
381
  settings.metronome.to_text
369
382
  end
370
383
 
384
+ get '/threads' do
385
+ content_type 'text/plain'
386
+ Thread.list.map do |t|
387
+ [
388
+ "#{t.name}: status=#{t.status}; alive=#{t.alive?}",
389
+ t.backtrace.nil? ? 'NO BACKTRACE' : " #{t.backtrace.join("\n ")}"
390
+ ].join("\n")
391
+ end.join("\n\n")
392
+ end
393
+
371
394
  not_found do
372
395
  status 404
373
396
  content_type 'text/plain'
@@ -24,7 +24,6 @@ require 'tempfile'
24
24
  require_relative '../log'
25
25
  require_relative '../size'
26
26
  require_relative '../wallet'
27
- require_relative '../atomic_file'
28
27
 
29
28
  # The entrance that ignores duplicates.
30
29
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -61,7 +60,7 @@ module Zold
61
60
  wallet.refurbish
62
61
  after = File.read(wallet.path)
63
62
  before = @wallets.find(id) do |w|
64
- w.exists? ? AtomicFile.new(w.path).read.to_s : ''
63
+ w.exists? ? File.read(w.path).to_s : ''
65
64
  end
66
65
  if before == after
67
66
  @log.info(
data/lib/zold/patch.rb CHANGED
@@ -23,7 +23,6 @@
23
23
  require_relative 'log'
24
24
  require_relative 'wallet'
25
25
  require_relative 'signature'
26
- require_relative 'atomic_file'
27
26
 
28
27
  # Patch.
29
28
  #
@@ -120,7 +119,7 @@ among #{payer.txns.count} transactions: #{txn.to_text}")
120
119
  def save(file, overwrite: false)
121
120
  raise 'You have to join at least one wallet in' if @id.nil?
122
121
  before = ''
123
- before = AtomicFile.new(file).read if File.exist?(file)
122
+ before = File.read(file) if File.exist?(file)
124
123
  wallet = Wallet.new(file)
125
124
  wallet.init(@id, @key, overwrite: overwrite, network: @network)
126
125
  File.open(file, 'a') do |f|
@@ -129,7 +128,7 @@ among #{payer.txns.count} transactions: #{txn.to_text}")
129
128
  end
130
129
  end
131
130
  wallet.refurbish
132
- after = AtomicFile.new(file).read
131
+ after = File.read(file)
133
132
  before != after
134
133
  end
135
134
  end
data/lib/zold/remotes.rb CHANGED
@@ -31,7 +31,6 @@ require_relative 'age'
31
31
  require_relative 'score'
32
32
  require_relative 'http'
33
33
  require_relative 'node/farm'
34
- require_relative 'atomic_file'
35
34
  require_relative 'type'
36
35
 
37
36
  # The list of remotes.
@@ -50,12 +49,10 @@ module Zold
50
49
  # Default number of nodes to fetch.
51
50
  MAX_NODES = 16
52
51
 
53
- # Mutex object
54
- MUTEX = Mutex.new
55
-
56
52
  attribute :file, Types::Strict::String
57
53
  attribute :network, Types::Strict::String.optional.default('test')
58
54
  attribute :timeout, Types::Strict::Integer.optional.default(16)
55
+ attribute :mutex, Types::Object.optional.default(Mutex.new)
59
56
 
60
57
  # Empty, for standalone mode
61
58
  class Empty < Remotes
@@ -183,7 +180,7 @@ module Zold
183
180
  list.each do |r|
184
181
  pool.post do
185
182
  Thread.current.abort_on_exception = true
186
- Thread.current.name = "remotes@#{r[:host]}:#{r[:port]}"
183
+ Thread.current.name = "remotes-#{idx}@#{r[:host]}:#{r[:port]}"
187
184
  start = Time.now
188
185
  begin
189
186
  yield Remotes::Remote.new(
@@ -194,7 +191,6 @@ module Zold
194
191
  log: log,
195
192
  network: network
196
193
  )
197
- idx += 1
198
194
  raise 'Took too long to execute' if (Time.now - start).round > timeout
199
195
  rescue StandardError => e
200
196
  error(r[:host], r[:port])
@@ -203,6 +199,7 @@ module Zold
203
199
  remove(r[:host], r[:port]) if errors > Remotes::TOLERANCE
204
200
  end
205
201
  end
202
+ idx += 1
206
203
  end
207
204
  pool.shutdown
208
205
  pool.kill unless pool.wait_for_termination(5 * 60)
@@ -230,7 +227,7 @@ module Zold
230
227
  private
231
228
 
232
229
  def modify
233
- Remotes::MUTEX.synchronize do
230
+ mutex.synchronize do
234
231
  save(yield(load))
235
232
  end
236
233
  end
@@ -261,7 +258,8 @@ module Zold
261
258
  end
262
259
 
263
260
  def save(list)
264
- AtomicFile.new(file).write(
261
+ File.write(
262
+ file,
265
263
  list.uniq { |r| "#{r[:host]}:#{r[:port]}" }.map do |r|
266
264
  [
267
265
  r[:host],
data/lib/zold/txns.rb CHANGED
@@ -21,7 +21,6 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require_relative 'txn'
24
- require_relative 'atomic_file'
25
24
 
26
25
  # Transactions in a wallet.
27
26
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -40,7 +39,7 @@ module Zold
40
39
 
41
40
  def fetch
42
41
  raise "Wallet file '#{@file}' is absent" unless File.exist?(@file)
43
- lines = AtomicFile.new(@file).read.split(/\n/)
42
+ lines = File.read(@file).split(/\n/)
44
43
  raise "Not enough lines in #{@file}, just #{lines.count}" if lines.count < 4
45
44
  lines.drop(5)
46
45
  .each_with_index
data/lib/zold/version.rb CHANGED
@@ -25,6 +25,6 @@
25
25
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
26
  # License:: MIT
27
27
  module Zold
28
- VERSION = '0.14.52'
28
+ VERSION = '0.14.53'
29
29
  PROTOCOL = 2
30
30
  end
data/lib/zold/wallet.rb CHANGED
@@ -30,7 +30,6 @@ require_relative 'tax'
30
30
  require_relative 'amount'
31
31
  require_relative 'hexnum'
32
32
  require_relative 'signature'
33
- require_relative 'atomic_file'
34
33
  require_relative 'txns'
35
34
  require_relative 'head'
36
35
 
@@ -90,7 +89,8 @@ module Zold
90
89
  def init(id, pubkey, overwrite: false, network: 'test')
91
90
  raise "File '#{@file}' already exists" if File.exist?(@file) && !overwrite
92
91
  raise "Invalid network name '#{network}'" unless network =~ /^[a-z]{4,16}$/
93
- AtomicFile.new(@file).write("#{network}\n#{PROTOCOL}\n#{id}\n#{pubkey.to_pub}\n\n")
92
+ FileUtils.mkdir_p(File.dirname(@file))
93
+ File.write(@file, "#{network}\n#{PROTOCOL}\n#{id}\n#{pubkey.to_pub}\n\n")
94
94
  @txns.flush
95
95
  @head.flush
96
96
  end
@@ -187,7 +187,8 @@ module Zold
187
187
  end
188
188
 
189
189
  def refurbish
190
- AtomicFile.new(@file).write(
190
+ File.write(
191
+ @file,
191
192
  "#{network}\n#{protocol}\n#{id}\n#{key.to_pub}\n\n#{txns.map { |t| t.to_s + "\n" }.join}"
192
193
  )
193
194
  @txns.flush
data/test/fake_home.rb CHANGED
@@ -28,7 +28,6 @@ require_relative '../lib/zold/sync_wallets'
28
28
  require_relative '../lib/zold/key'
29
29
  require_relative '../lib/zold/version'
30
30
  require_relative '../lib/zold/remotes'
31
- require_relative '../lib/zold/atomic_file'
32
31
 
33
32
  # Fake home dir.
34
33
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -74,7 +73,7 @@ class FakeHome
74
73
  mtime: wallet.mtime.utc.iso8601,
75
74
  digest: wallet.digest,
76
75
  balance: wallet.balance.to_i,
77
- body: Zold::AtomicFile.new(wallet.path).read
76
+ body: File.read(wallet.path)
78
77
  }.to_json
79
78
  end
80
79
  end
@@ -61,7 +61,8 @@ class FrontTest < Minitest::Test
61
61
  '/farm',
62
62
  '/metronome',
63
63
  '/score',
64
- '/trace'
64
+ '/trace',
65
+ '/threads'
65
66
  ],
66
67
  '404' => [
67
68
  '/this-is-absent',
@@ -121,6 +122,27 @@ class FrontTest < Minitest::Test
121
122
  end
122
123
  end
123
124
 
125
+ def test_fetch_in_multiple_threads
126
+ FakeNode.new(log: test_log).run(['--no-metronome']) do |port|
127
+ FakeHome.new.run do |home|
128
+ wallet = home.create_wallet
129
+ base = "http://localhost:#{port}"
130
+ Zold::Http.new(uri: "#{base}/wallet/#{wallet.id}", score: nil).put(File.read(wallet.path))
131
+ assert_equal_wait('200') { Zold::Http.new(uri: "#{base}/wallet/#{wallet.id}", score: nil).get.code }
132
+ threads = []
133
+ mutex = Mutex.new
134
+ assert_in_threads(loops: 100) do
135
+ assert_equal_wait('200') do
136
+ res = Zold::Http.new(uri: "#{base}/wallet/#{wallet.id}", score: nil).get
137
+ mutex.synchronize { threads << res.header['X-Zold-Thread'] }
138
+ res.code
139
+ end
140
+ end
141
+ assert(threads.uniq.count > 1)
142
+ end
143
+ end
144
+ end
145
+
124
146
  def test_pushes_twice
125
147
  FakeNode.new(log: test_log).run do |port|
126
148
  FakeHome.new.run do |home|
@@ -221,26 +243,12 @@ class FrontTest < Minitest::Test
221
243
  Time.stub :now, Time.at(0) do
222
244
  FakeNode.new(log: test_log).run(['--ignore-score-weakness']) do |port|
223
245
  response = Zold::Http.new(uri: URI("http://localhost:#{port}/"), score: nil).get
224
- assert_equal(
225
- 'no-cache',
226
- response.header['Cache-Control']
227
- )
228
- assert_equal(
229
- 'close',
230
- response.header['Connection']
231
- )
232
- assert_equal(
233
- app.settings.version,
234
- response.header['X-Zold-Version']
235
- )
236
- assert_equal(
237
- app.settings.protocol.to_s,
238
- response.header[Zold::Http::PROTOCOL_HEADER]
239
- )
240
- assert_equal(
241
- '*',
242
- response.header['Access-Control-Allow-Origin']
243
- )
246
+ assert_equal('no-cache', response.header['Cache-Control'])
247
+ assert_equal('close', response.header['Connection'])
248
+ assert_equal(app.settings.version, response.header['X-Zold-Version'])
249
+ assert_equal(app.settings.protocol.to_s, response.header[Zold::Http::PROTOCOL_HEADER])
250
+ assert_equal('*', response.header['Access-Control-Allow-Origin'])
251
+ assert(response.header['X-Zold-Milliseconds'])
244
252
  assert(!response.header[Zold::Http::SCORE_HEADER].nil?)
245
253
  end
246
254
  end
data/test/test_zold.rb CHANGED
@@ -34,7 +34,7 @@ require_relative '../lib/zold/version'
34
34
  class TestZold < Minitest::Test
35
35
  def test_all_scripts
36
36
  Dir.new('fixtures/scripts').select { |f| f =~ /\.sh$/ && !f.start_with?('_') }.each do |f|
37
- # next unless f == 'redeploy-on-upgrade.sh'
37
+ # next unless f == 'deadlocks.sh'
38
38
  Dir.mktmpdir do |dir|
39
39
  FileUtils.cp('fixtures/id_rsa.pub', dir)
40
40
  FileUtils.cp('fixtures/id_rsa', dir)
data/zold.gemspec CHANGED
@@ -65,6 +65,7 @@ and suggests a different architecture for digital wallet maintenance.'
65
65
  s.add_runtime_dependency 'json', '~>1.8'
66
66
  s.add_runtime_dependency 'moneta', '~>1.0'
67
67
  s.add_runtime_dependency 'openssl', '~>2.1'
68
+ s.add_runtime_dependency 'puma'
68
69
  s.add_runtime_dependency 'rainbow', '~>3.0'
69
70
  s.add_runtime_dependency 'rake', '~>12.3' # has to stay here for Heroku
70
71
  s.add_runtime_dependency 'rubocop', '0.58.1' # has to stay here for Heroku
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.52
4
+ version: 0.14.53
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-17 00:00:00.000000000 Z
11
+ date: 2018-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backtrace
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - "~>"
165
165
  - !ruby/object:Gem::Version
166
166
  version: '2.1'
167
+ - !ruby/object:Gem::Dependency
168
+ name: puma
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: rainbow
169
183
  requirement: !ruby/object:Gem::Requirement
@@ -488,7 +502,6 @@ files:
488
502
  - lib/zold.rb
489
503
  - lib/zold/age.rb
490
504
  - lib/zold/amount.rb
491
- - lib/zold/atomic_file.rb
492
505
  - lib/zold/cached_wallets.rb
493
506
  - lib/zold/commands/alias.rb
494
507
  - lib/zold/commands/args.rb
@@ -587,7 +600,6 @@ files:
587
600
  - test/test__helper.rb
588
601
  - test/test_age.rb
589
602
  - test/test_amount.rb
590
- - test/test_atomic_file.rb
591
603
  - test/test_cached_wallets.rb
592
604
  - test/test_copies.rb
593
605
  - test/test_gem.rb
@@ -684,7 +696,6 @@ test_files:
684
696
  - test/test__helper.rb
685
697
  - test/test_age.rb
686
698
  - test/test_amount.rb
687
- - test/test_atomic_file.rb
688
699
  - test/test_cached_wallets.rb
689
700
  - test/test_copies.rb
690
701
  - test/test_gem.rb
@@ -1,52 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2018 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
22
-
23
- # Atomic file.
24
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
- # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
- # License:: MIT
27
- module Zold
28
- # Atomic file
29
- class AtomicFile
30
- def initialize(file)
31
- raise 'File can\'t be nil' if file.nil?
32
- @file = file
33
- @mutex = Mutex.new
34
- end
35
-
36
- def read
37
- @mutex.synchronize do
38
- File.open(@file, 'rb', &:read)
39
- end
40
- end
41
-
42
- def write(content)
43
- raise 'Content can\'t be nil' if content.nil?
44
- FileUtils.mkdir_p(File.dirname(@file))
45
- @mutex.synchronize do
46
- File.open(@file, 'wb') do |f|
47
- f.write(content)
48
- end
49
- end
50
- end
51
- end
52
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright (c) 2018 Yegor Bugayenko
4
- #
5
- # Permission is hereby granted, free of charge, to any person obtaining a copy
6
- # of this software and associated documentation files (the 'Software'), to deal
7
- # in the Software without restriction, including without limitation the rights
8
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- # copies of the Software, and to permit persons to whom the Software is
10
- # furnished to do so, subject to the following conditions:
11
- #
12
- # The above copyright notice and this permission notice shall be included in all
13
- # copies or substantial portions of the Software.
14
- #
15
- # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- # SOFTWARE.
22
-
23
- require 'minitest/autorun'
24
- require 'tmpdir'
25
- require 'securerandom'
26
- require_relative 'test__helper'
27
- require_relative '../lib/zold/atomic_file'
28
- require_relative '../lib/zold/verbose_thread'
29
-
30
- # AtomicFile test.
31
- # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
- # Copyright:: Copyright (c) 2018 Yegor Bugayenko
33
- # License:: MIT
34
- class TestAtomicFile < Minitest::Test
35
- def test_writes_and_reads
36
- Dir.mktmpdir do |dir|
37
- file = Zold::AtomicFile.new(File.join(dir, 'test.txt'))
38
- ['', 'hello, dude!'].each do |t|
39
- file.write(t)
40
- assert_equal(t, file.read)
41
- end
42
- end
43
- end
44
-
45
- def test_writes_from_many_threads
46
- Dir.mktmpdir do |dir|
47
- file = Zold::AtomicFile.new(File.join(dir, 'a.txt'))
48
- content = SecureRandom.hex(1000)
49
- assert_in_threads(loops: 1000) do
50
- file.write(content)
51
- assert_equal(content, file.read, 'Invalid content')
52
- end
53
- end
54
- end
55
- end