zold 0.14.26 → 0.14.27

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
  SHA1:
3
- metadata.gz: f4395e269936df99f2f80289e47287be347e2f3a
4
- data.tar.gz: 5719f9f81b7c4c70860c67a1903cfc01b5dbe95c
3
+ metadata.gz: ff2a076e21158840c47f865ccaf53b4c590f7920
4
+ data.tar.gz: 800ce350438a7f9424de3b85c75f04ac17866efd
5
5
  SHA512:
6
- metadata.gz: b8849738bb1417903741cb729467ae2f96e1a244578a8702a0e4b0d69f674ba710587c17bed2ff023b639aed6d0825748f4e970f371dd7360a3d3692ab6a9898
7
- data.tar.gz: 4d84feac840f4e4992025e8938ce24a62ec3d56d14b5393460984e8d8de907d8295f1b07b6f485b19f2e03342ff113d9dbeb4ceed5c0159229a4c0357dfe5f42
6
+ metadata.gz: 58adc79b2927b63f4168112709f80469fbea9153656e181d8604c42e2e37ad44d017a4251fe5d60b3c5449b0d23e5173bdd89fb87527ce000d8203b6e29d5e14
7
+ data.tar.gz: 2d1bb0ee453fbeaba2829ab37b233e67127c3819347052778d6a0c853608aada5d9bfcfd9e5700611efef4bafc987c6ab88f78e6945d6da6e8715401a021067c
@@ -13,7 +13,18 @@ environment:
13
13
  - RUBY_VERSION: 24
14
14
  - RUBY_VERSION: 25
15
15
  install:
16
- - cmd: set PATH=C:\Ruby%RUBY_VERSION%-X64\bin;%PATH%
16
+ - ps: |
17
+ $Env:PATH = "C:\Ruby${Env:ruby_version}-X64\bin;${Env:PATH}"
18
+ if ($Env:ruby_version -match "^23" ) {
19
+ # RubyInstaller; download OpenSSL headers from OpenKnapsack Project
20
+ $Env:openssl_dir = "C:\Ruby${Env:ruby_version}"
21
+ appveyor DownloadFile http://dl.bintray.com/oneclick/OpenKnapsack/x64/openssl-1.0.2j-x64-windows.tar.lzma
22
+ 7z e openssl-1.0.2j-x64-windows.tar.lzma
23
+ 7z x -y -oC:\Ruby${Env:ruby_version} openssl-1.0.2j-x64-windows.tar
24
+ } else {
25
+ # RubyInstaller2; openssl package seems to be installed already
26
+ $Env:openssl_dir = "C:\msys64\mingw64"
27
+ }
17
28
  - cmd: ruby --version
18
29
  - cmd: git --version
19
30
  - cmd: bundle config --local path vendor/bundle
@@ -0,0 +1,49 @@
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 'time'
24
+
25
+ # Age
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
28
+ # License:: MIT
29
+ module Zold
30
+ # Age
31
+ class Age
32
+ def initialize(time)
33
+ @time = time
34
+ @time = Time.parse(@time) unless time.is_a?(Time)
35
+ end
36
+
37
+ def to_s
38
+ return '?' if @time.nil?
39
+ sec = Time.now - @time
40
+ if sec < 60
41
+ "#{sec.round(2)}s"
42
+ elsif sec < 60 * 60
43
+ "#{(sec / 60).round}m"
44
+ else
45
+ "#{(sec / 3600).round}h"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -26,6 +26,7 @@ require 'time'
26
26
  require 'slop'
27
27
  require 'rainbow'
28
28
  require_relative 'args'
29
+ require_relative '../age'
29
30
  require_relative '../log'
30
31
  require_relative '../http'
31
32
  require_relative '../score'
@@ -63,7 +64,7 @@ Available options:"
63
64
  cps.all.each do |c|
64
65
  wallet = Wallet.new(c[:path])
65
66
  @log.debug(" #{c[:name]}: #{c[:score]} #{wallet.balance}/#{wallet.txns.count}t/\
66
- #{wallet.digest[0, 6]}/#{File.size(c[:path])}b")
67
+ #{wallet.digest[0, 6]}/#{File.size(c[:path])}b/#{Age.new(File.mtime(c[:path]))}")
67
68
  end
68
69
  end
69
70
  end
@@ -26,8 +26,10 @@ require 'time'
26
26
  require 'tempfile'
27
27
  require 'slop'
28
28
  require 'rainbow'
29
+ require 'concurrent/atomics'
29
30
  require_relative 'args'
30
31
  require_relative '../log'
32
+ require_relative '../age'
31
33
  require_relative '../http'
32
34
  require_relative '../score'
33
35
  require_relative '../json_page'
@@ -75,18 +77,24 @@ Available options:"
75
77
  private
76
78
 
77
79
  def fetch(id, cps, opts)
78
- total = 0
79
- nodes = 0
80
- done = 0
80
+ total = Concurrent::AtomicFixnum.new
81
+ nodes = Concurrent::AtomicFixnum.new
82
+ done = Concurrent::AtomicFixnum.new
81
83
  @remotes.iterate(@log) do |r|
82
- nodes += 1
83
- total += fetch_one(id, r, cps, opts)
84
- done += 1
84
+ nodes.increment
85
+ total.increment(fetch_one(id, r, cps, opts))
86
+ done.increment
87
+ end
88
+ raise "There are no remote nodes, run 'zold remote reset'" if nodes.value.zero?
89
+ raise "No nodes out of #{nodes.value} have the wallet #{id}" if done.value.zero? && !opts['quiet-if-absent']
90
+ @log.info("#{done.value} copies of #{id} fetched with the total score of \
91
+ #{total.value} from #{nodes.value} nodes")
92
+ @log.debug("#{cps.all.count} local copies:")
93
+ cps.all.each do |c|
94
+ wallet = Wallet.new(c[:path])
95
+ @log.debug(" #{c[:name]}: #{c[:score]} #{wallet.balance}/#{wallet.txns.count}t/\
96
+ #{wallet.digest[0, 6]}/#{File.size(c[:path])}b/#{Age.new(File.mtime(c[:path]))}")
85
97
  end
86
- raise "There are no remote nodes, run 'zold remote reset'" if nodes.zero?
87
- raise "No nodes out of #{nodes} have the wallet #{id}" if done.zero? && !opts['quiet-if-absent']
88
- @log.info("#{done} copies of #{id} fetched for the total score of #{total} from #{nodes} nodes")
89
- @log.debug("#{cps.all.count} local copies:\n #{cps.all.map { |c| "#{c[:name]}: #{c[:score]}" }.join("\n ")}")
90
98
  end
91
99
 
92
100
  def fetch_one(id, r, cps, opts)
@@ -118,7 +126,8 @@ Available options:"
118
126
  raise "The balance of #{id} is #{wallet.balance} and it's not a root wallet"
119
127
  end
120
128
  copy = cps.add(File.read(f), score.host, score.port, score.value)
121
- @log.info("#{r} returned #{body.length}b/#{wallet.balance}/#{wallet.txns.count}t/#{digest(json)}/#{age(json)} \
129
+ @log.info("#{r} returned #{body.length}b/#{wallet.balance}/#{wallet.txns.count}t/\
130
+ #{digest(json)}/#{Age.new(json['mtime'])} \
122
131
  as copy #{copy} of #{id} in #{(Time.now - start).round(2)}s: #{Rainbow(score.value).green} (#{json['version']})")
123
132
  end
124
133
  score.value
@@ -129,18 +138,5 @@ as copy #{copy} of #{id} in #{(Time.now - start).round(2)}s: #{Rainbow(score.val
129
138
  return '?' if hash.nil?
130
139
  hash[0, 6]
131
140
  end
132
-
133
- def age(json)
134
- mtime = json['mtime']
135
- return '?' if mtime.nil?
136
- sec = Time.now - Time.parse(mtime)
137
- if sec < 60
138
- "#{sec.round(2)}s"
139
- elsif sec < 60 * 60
140
- "#{(sec / 60).round}m"
141
- else
142
- "#{(sec / 3600).round}h"
143
- end
144
- end
145
141
  end
146
142
  end
@@ -84,10 +84,10 @@ Available options:"
84
84
  end
85
85
  modified = patch.save(wallet.path, overwrite: true)
86
86
  if modified
87
- @log.debug("#{cps.count} copies with the total score of #{score} successfully merged \
87
+ @log.info("#{cps.count} copies with the total score of #{score} successfully merged \
88
88
  into #{wallet.id}/#{wallet.balance}/#{wallet.txns.count}t")
89
89
  else
90
- @log.debug("Nothing changed in #{wallet.id} after merge of #{cps.count} copies")
90
+ @log.info("Nothing changed in #{wallet.id} after merge of #{cps.count} copies")
91
91
  end
92
92
  modified
93
93
  end
@@ -123,6 +123,9 @@ module Zold
123
123
  'Don\'t run the metronome',
124
124
  required: true,
125
125
  default: false
126
+ o.string '--alias',
127
+ 'The alias of the node (default: host:port)',
128
+ require: false
126
129
  o.bool '--help', 'Print instructions'
127
130
  end
128
131
  if opts.help?
@@ -171,6 +174,12 @@ module Zold
171
174
  Front.set(:dump_errors, opts['dump-errors'])
172
175
  Front.set(:port, opts['bind-port'])
173
176
  Front.set(:reboot, !opts['never-reboot'])
177
+ node_alias = opts[:alias] || address
178
+ unless node_alias.eql?(address)
179
+ re = Regexp.new(/^[a-z0-9]{4,16}$/)
180
+ raise '--alias should be a 4 to 16 char long alphanumeric string' unless re.match(node_alias)
181
+ end
182
+ Front.set(:node_alias, node_alias)
174
183
  invoice = opts[:invoice]
175
184
  unless invoice.include?('@')
176
185
  if @wallets.find(Id.new(invoice)).exists?
@@ -48,11 +48,16 @@ module Zold
48
48
  to_s.hash
49
49
  end
50
50
 
51
- def==(other)
51
+ def ==(other)
52
52
  raise 'Can only compare with Id' unless other.is_a?(Id)
53
53
  to_s == other.to_s
54
54
  end
55
55
 
56
+ def <=>(other)
57
+ raise 'Can only compare with Id' unless other.is_a?(Id)
58
+ to_s <=> other.to_s
59
+ end
60
+
56
61
  def to_str
57
62
  to_s
58
63
  end
@@ -67,6 +67,7 @@ module Zold
67
67
  set :wallets, nil? # to be injected at node.rb
68
68
  set :remotes, nil? # to be injected at node.rb
69
69
  set :copies, nil? # to be injected at node.rb
70
+ set :node_alias, nil? # to be injected at node.rb
70
71
  end
71
72
  use Rack::Deflater
72
73
 
@@ -150,6 +151,7 @@ while #{settings.address} is in '#{settings.network}'"
150
151
  content_type 'application/json'
151
152
  JSON.pretty_generate(
152
153
  version: settings.version,
154
+ alias: settings.node_alias,
153
155
  network: settings.network,
154
156
  protocol: settings.protocol,
155
157
  score: score.to_h,
@@ -177,11 +179,13 @@ while #{settings.address} is in '#{settings.network}'"
177
179
  content_type 'application/json'
178
180
  {
179
181
  version: settings.version,
182
+ alias: settings.node_alias,
180
183
  protocol: settings.protocol,
181
184
  id: wallet.id.to_s,
182
185
  score: score.to_h,
183
186
  wallets: settings.wallets.all.count,
184
187
  mtime: wallet.mtime.utc.iso8601,
188
+ size: File.size(wallet.path),
185
189
  digest: wallet.digest,
186
190
  balance: wallet.balance.to_i,
187
191
  body: AtomicFile.new(wallet.path).read
@@ -195,6 +199,7 @@ while #{settings.address} is in '#{settings.network}'"
195
199
  content_type 'application/json'
196
200
  {
197
201
  version: settings.version,
202
+ alias: settings.node_alias,
198
203
  protocol: settings.protocol,
199
204
  id: wallet.id.to_s,
200
205
  score: score.to_h,
@@ -255,11 +260,20 @@ while #{settings.address} is in '#{settings.network}'"
255
260
  '--',
256
261
  "Balance: #{wallet.balance.to_zld}",
257
262
  "Transactions: #{wallet.txns.count}",
263
+ "Wallet size: #{File.size(wallet.path)} bytes",
258
264
  "Modified: #{wallet.mtime.utc.iso8601}",
259
265
  "Digest: #{wallet.digest}"
260
266
  ].join("\n")
261
267
  end
262
268
 
269
+ get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.bin} do
270
+ id = Id.new(params[:id])
271
+ wallet = settings.wallets.find(id)
272
+ error 404 unless wallet.exists?
273
+ content_type 'text/plain'
274
+ AtomicFile.new(wallet.path).read
275
+ end
276
+
263
277
  put %r{/wallet/(?<id>[A-Fa-f0-9]{16})/?} do
264
278
  request.body.rewind
265
279
  modified = settings.entrance.push(Id.new(params[:id]), request.body.read.to_s)
@@ -269,6 +283,7 @@ while #{settings.address} is in '#{settings.network}'"
269
283
  end
270
284
  JSON.pretty_generate(
271
285
  version: settings.version,
286
+ alias: settings.node_alias,
272
287
  score: score.to_h,
273
288
  wallets: settings.wallets.all.count
274
289
  )
@@ -278,6 +293,7 @@ while #{settings.address} is in '#{settings.network}'"
278
293
  content_type 'application/json'
279
294
  JSON.pretty_generate(
280
295
  version: settings.version,
296
+ alias: settings.node_alias,
281
297
  score: score.to_h,
282
298
  all: settings.remotes.all
283
299
  )
@@ -125,6 +125,7 @@ among #{payer.txns.count} transactions: #{txn.to_text}")
125
125
  wallet = Wallet.new(file)
126
126
  wallet.init(@id, @key, overwrite: overwrite, network: @network)
127
127
  @txns.each { |t| wallet.add(t) }
128
+ wallet.refurbish
128
129
  after = AtomicFile.new(file).read
129
130
  before != after
130
131
  end
@@ -75,6 +75,11 @@ module Zold
75
75
  details == other.details && sign == other.sign
76
76
  end
77
77
 
78
+ def <=>(other)
79
+ raise 'Can only compare with Txn' unless other.is_a?(Txn)
80
+ [date, amount * -1, id, bnf] <=> [other.date, other.amount * -1, other.id, other.bnf]
81
+ end
82
+
78
83
  def to_s
79
84
  [
80
85
  Hexnum.new(@id, 4).to_s,
@@ -101,6 +106,9 @@ module Zold
101
106
  t
102
107
  end
103
108
 
109
+ # Sign the transaction and add RSA signature to it
110
+ # +pvt+:: The private RSA key of the paying wallet
111
+ # +id+:: Paying wallet ID
104
112
  def signed(pvt, id)
105
113
  t = clone
106
114
  t.sign = Signature.new.sign(pvt, id, self)
@@ -25,6 +25,6 @@
25
25
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
26
  # License:: MIT
27
27
  module Zold
28
- VERSION = '0.14.26'
28
+ VERSION = '0.14.27'
29
29
  PROTOCOL = 2
30
30
  end
@@ -28,6 +28,7 @@ require_relative 'id'
28
28
  require_relative 'txn'
29
29
  require_relative 'tax'
30
30
  require_relative 'amount'
31
+ require_relative 'hexnum'
31
32
  require_relative 'signature'
32
33
  require_relative 'atomic_file'
33
34
 
@@ -168,7 +169,7 @@ module Zold
168
169
  lines.drop(5)
169
170
  .each_with_index
170
171
  .map { |line, i| Txn.parse(line, i + 6) }
171
- .sort_by { |t| [t.date, t.amount * -1] }
172
+ .sort
172
173
  end
173
174
 
174
175
  def refurbish
@@ -23,6 +23,7 @@
23
23
  require 'minitest/autorun'
24
24
  require 'json'
25
25
  require 'time'
26
+ require 'securerandom'
26
27
  require_relative '../test__helper'
27
28
  require_relative 'fake_node'
28
29
  require_relative '../fake_home'
@@ -217,4 +218,44 @@ class FrontTest < Minitest::Test
217
218
  end
218
219
  end
219
220
  end
221
+
222
+ def test_alias_parameter
223
+ name = SecureRandom.hex(4)
224
+ FakeNode.new(log: test_log).run(['--ignore-score-weakness', "--alias=#{name}"]) do |port|
225
+ [
226
+ '/',
227
+ '/remotes'
228
+ ].each do |path|
229
+ uri = URI("http://localhost:#{port}#{path}")
230
+ response = Zold::Http.new(uri: uri, score: nil).get
231
+ assert_match(
232
+ name,
233
+ Zold::JsonPage.new(response.body).to_hash['alias'].to_s,
234
+ response.body
235
+ )
236
+ end
237
+ end
238
+ end
239
+
240
+ def test_default_alias_parameter
241
+ FakeNode.new(log: test_log).run(['--ignore-score-weakness']) do |port|
242
+ uri = URI("http://localhost:#{port}/")
243
+ response = Zold::Http.new(uri: uri, score: nil).get
244
+ assert_match(
245
+ "localhost:#{port}",
246
+ Zold::JsonPage.new(response.body).to_hash['alias'].to_s,
247
+ response.body
248
+ )
249
+ end
250
+ end
251
+
252
+ def test_invalid_alias
253
+ exception = assert_raises RuntimeError do
254
+ FakeNode.new(log: test_log).run(['--ignore-score-weakness', '--alias=invalid-alias']) do |port|
255
+ uri = URI("http://localhost:#{port}/")
256
+ Zold::Http.new(uri: uri, score: nil).get
257
+ end
258
+ end
259
+ assert_equal('--alias should be a 4 to 16 char long alphanumeric string', exception.message)
260
+ end
220
261
  end
@@ -106,4 +106,38 @@ class TestPatch < Minitest::Test
106
106
  assert_equal(Zold::Amount.new(zld: 2.0), first.balance)
107
107
  end
108
108
  end
109
+
110
+ def test_merges_fragmented_parts
111
+ FakeHome.new.run do |home|
112
+ first = home.create_wallet(Zold::Id::ROOT)
113
+ second = home.create_wallet
114
+ File.write(second.path, File.read(first.path))
115
+ key = Zold::Key.new(file: 'fixtures/id_rsa')
116
+ start = Time.parse('2017-07-19T21:24:51Z')
117
+ first.add(
118
+ Zold::Txn.new(
119
+ 1, start, Zold::Amount.new(zld: -2.0),
120
+ 'NOPREFIX', Zold::Id.new, 'first payment'
121
+ ).signed(key, first.id)
122
+ )
123
+ second.add(
124
+ Zold::Txn.new(
125
+ 2, start + 1, Zold::Amount.new(zld: -2.0),
126
+ 'NOPREFIX', Zold::Id.new, 'second payment'
127
+ ).signed(key, first.id)
128
+ )
129
+ first.add(
130
+ Zold::Txn.new(
131
+ 3, start + 2, Zold::Amount.new(zld: -2.0),
132
+ 'NOPREFIX', Zold::Id.new, 'third payment'
133
+ ).signed(key, first.id)
134
+ )
135
+ patch = Zold::Patch.new(home.wallets, log: test_log)
136
+ patch.join(first)
137
+ patch.join(second)
138
+ FileUtils.rm(first.path)
139
+ assert_equal(true, patch.save(first.path))
140
+ assert_equal(Zold::Amount.new(zld: -6.0), first.balance)
141
+ end
142
+ end
109
143
  end
@@ -34,6 +34,14 @@ require_relative '../lib/zold/commands/pay'
34
34
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
35
35
  # License:: MIT
36
36
  class TestWallet < Minitest::Test
37
+ def test_reads_empty_wallet
38
+ FakeHome.new.run do |home|
39
+ wallet = home.create_wallet
40
+ assert(wallet.txns.empty?)
41
+ assert_equal(Zold::Amount::ZERO, wallet.balance)
42
+ end
43
+ end
44
+
37
45
  def test_adds_transaction
38
46
  FakeHome.new.run do |home|
39
47
  wallet = home.create_wallet
@@ -171,4 +179,33 @@ class TestWallet < Minitest::Test
171
179
  )
172
180
  end
173
181
  end
182
+
183
+ def test_sorts_them_always_right
184
+ FakeHome.new.run do |home|
185
+ time = Time.now
186
+ txns = []
187
+ 50.times do
188
+ txns << Zold::Txn.new(
189
+ 1,
190
+ time,
191
+ Zold::Amount.new(zld: 1.99),
192
+ 'NOPREFIX', Zold::Id.new, '-'
193
+ )
194
+ end
195
+ wallet = home.create_wallet
196
+ empty = File.read(wallet.path)
197
+ text = ''
198
+ 10.times do
199
+ File.write(wallet.path, empty)
200
+ txns.shuffle!
201
+ txns.each { |t| wallet.add(t) }
202
+ wallet.refurbish
203
+ if text.empty?
204
+ text = File.read(wallet.path)
205
+ next
206
+ end
207
+ assert_equal(text, File.read(wallet.path))
208
+ end
209
+ end
210
+ end
174
211
  end
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.26
4
+ version: 0.14.27
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-08-01 00:00:00.000000000 Z
11
+ date: 2018-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -400,6 +400,7 @@ files:
400
400
  - fixtures/scripts/spread-wallets.sh
401
401
  - heroku-run.sh
402
402
  - lib/zold.rb
403
+ - lib/zold/age.rb
403
404
  - lib/zold/amount.rb
404
405
  - lib/zold/atomic_file.rb
405
406
  - lib/zold/backtrace.rb