zold 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -1
  3. data/bin/zold +21 -81
  4. data/fixtures/keys/1.pub +1 -0
  5. data/fixtures/keys/2 +51 -0
  6. data/fixtures/keys/2.pub +1 -0
  7. data/fixtures/scripts/print-helps.sh +15 -0
  8. data/fixtures/scripts/push-and-pull.sh +7 -3
  9. data/lib/zold/commands/clean.rb +24 -7
  10. data/lib/zold/commands/create.rb +16 -4
  11. data/lib/zold/commands/diff.rb +32 -9
  12. data/lib/zold/commands/fetch.rb +36 -9
  13. data/lib/zold/commands/invoice.rb +64 -0
  14. data/lib/zold/commands/list.rb +1 -1
  15. data/lib/zold/commands/merge.rb +31 -10
  16. data/lib/zold/commands/node.rb +6 -2
  17. data/lib/zold/commands/pay.rb +40 -17
  18. data/lib/zold/commands/propagate.rb +34 -14
  19. data/lib/zold/commands/push.rb +27 -9
  20. data/lib/zold/commands/remote.rb +63 -34
  21. data/lib/zold/commands/show.rb +34 -9
  22. data/lib/zold/copies.rb +4 -0
  23. data/lib/zold/http.rb +1 -1
  24. data/lib/zold/key.rb +17 -11
  25. data/lib/zold/node/farm.rb +8 -0
  26. data/lib/zold/node/front.rb +13 -3
  27. data/lib/zold/patch.rb +8 -8
  28. data/lib/zold/prefixes.rb +53 -0
  29. data/lib/zold/remotes.rb +6 -0
  30. data/lib/zold/score.rb +4 -4
  31. data/lib/zold/signature.rb +9 -9
  32. data/lib/zold/txn.rb +111 -0
  33. data/lib/zold/version.rb +1 -1
  34. data/lib/zold/wallet.rb +32 -59
  35. data/lib/zold/wallets.rb +2 -2
  36. data/test/commands/test_clean.rb +5 -4
  37. data/test/commands/test_create.rb +3 -3
  38. data/test/commands/test_diff.rb +16 -15
  39. data/test/commands/test_fetch.rb +13 -11
  40. data/test/commands/test_invoice.rb +46 -0
  41. data/test/commands/test_list.rb +4 -4
  42. data/test/commands/test_merge.rb +21 -22
  43. data/test/commands/test_node.rb +9 -9
  44. data/test/commands/test_pay.rb +12 -12
  45. data/test/commands/test_remote.rb +11 -11
  46. data/test/commands/test_show.rb +9 -7
  47. data/test/node/fake_node.rb +3 -3
  48. data/test/node/test_farm.rb +1 -1
  49. data/test/node/test_front.rb +6 -6
  50. data/test/test_amount.rb +1 -1
  51. data/test/test_copies.rb +1 -1
  52. data/test/test_http.rb +1 -1
  53. data/test/test_id.rb +1 -1
  54. data/test/test_key.rb +19 -1
  55. data/test/test_patch.rb +11 -11
  56. data/test/test_prefixes.rb +46 -0
  57. data/test/test_remotes.rb +1 -1
  58. data/test/test_score.rb +1 -1
  59. data/test/test_signature.rb +9 -11
  60. data/test/test_wallet.rb +22 -21
  61. data/test/test_wallets.rb +4 -4
  62. metadata +12 -1
@@ -19,7 +19,7 @@
19
19
  # SOFTWARE.
20
20
 
21
21
  require 'slop'
22
- require_relative '../log.rb'
22
+ require_relative '../log'
23
23
 
24
24
  # PAY command.
25
25
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -28,30 +28,53 @@ require_relative '../log.rb'
28
28
  module Zold
29
29
  # Money sending command
30
30
  class Pay
31
- def initialize(payer:, receiver:, amount:,
32
- pvtkey:, details: '-', log: Log::Quiet.new)
33
- @payer = payer
34
- @receiver = receiver
35
- @amount = amount
31
+ def initialize(wallets:, pvtkey:, log: Log::Quiet.new)
32
+ @wallets = wallets
36
33
  @pvtkey = pvtkey
37
- @details = details
38
34
  @log = log
39
35
  end
40
36
 
41
37
  def run(args = [])
42
- opts = Slop.parse(args) do |o|
43
- o.bool '--force', 'Ignore all validations'
38
+ opts = Slop.parse(args, help: true) do |o|
39
+ o.banner = "Usage: zold pay wallet invoice amount [details] [options]
40
+ Where:
41
+ 'wallet' is the sender's wallet ID
42
+ 'invoice' is the beneficiary's invoice number, generated by 'zold invoice'
43
+ 'amount' is the amount to pay, in ZLD, for example '14.95'
44
+ 'details' is the optional text to attach to the payment
45
+ Available options:"
46
+ o.bool '--force',
47
+ 'Ignore all validations',
48
+ default: false
49
+ o.bool '--help', 'Print instructions'
44
50
  end
45
- unless opts['force']
46
- raise "The amount can't be negative: #{@amount}" if @amount.negative?
47
- if !@payer.root? && @payer.balance < @amount
48
- raise "There is not enough funds in #{@payer} to send #{@amount}, \
49
- only #{@payer.balance} left"
51
+ if opts.help?
52
+ @log.info(opts.to_s)
53
+ return
54
+ end
55
+ raise 'Payer wallet ID is required' if opts.arguments[0].nil?
56
+ from = @wallets.find(Zold::Id.new(opts.arguments[0]))
57
+ raise 'Wallet doesn\'t exist, do \'fetch\' first' unless from.exists?
58
+ raise 'Recepient\'s invoice is required' if opts.arguments[1].nil?
59
+ invoice = opts.arguments[1]
60
+ raise 'Amount is required (in ZLD)' if opts.arguments[2].nil?
61
+ amount = Zold::Amount.new(zld: opts.arguments[2].to_f)
62
+ details = opts.arguments[3] ? opts.arguments[3] : '-'
63
+ pay(from, invoice, amount, details, opts)
64
+ end
65
+
66
+ def pay(from, invoice, amount, details, opts)
67
+ unless opts.force?
68
+ raise 'The amount can\'t be zero' if amount.zero?
69
+ raise "The amount can't be negative: #{amount}" if amount.negative?
70
+ if !from.root? && from.balance < @amount
71
+ raise "There is not enough funds in #{from} to send #{amount}, \
72
+ only #{payer.balance} left"
50
73
  end
51
74
  end
52
- txn = @payer.sub(@amount, @receiver, @pvtkey, @details)
53
- @log.debug("#{@amount} sent from #{@payer} to #{@receiver}: #{@details}")
54
- @log.info(txn[:id])
75
+ txn = from.sub(amount, invoice, @pvtkey, details)
76
+ @log.debug("#{amount} sent from #{from} to #{invoice}: #{details}")
77
+ @log.info(txn.id)
55
78
  txn
56
79
  end
57
80
  end
@@ -18,9 +18,10 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- require_relative '../log.rb'
22
- require_relative '../wallet.rb'
23
- require_relative '../wallets.rb'
21
+ require_relative '../log'
22
+ require_relative '../wallet'
23
+ require_relative '../wallets'
24
+ require_relative '../prefixes'
24
25
 
25
26
  # PROPAGATE command.
26
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -29,25 +30,44 @@ require_relative '../wallets.rb'
29
30
  module Zold
30
31
  # PROPAGATE pulling command
31
32
  class Propagate
32
- def initialize(wallet:, wallets:, log: Log::Quiet.new)
33
- @wallet = wallet
33
+ def initialize(wallets:, log: Log::Quiet.new)
34
34
  @wallets = wallets
35
35
  @log = log
36
36
  end
37
37
 
38
- def run(_ = [])
39
- me = @wallet.id
40
- @wallet.txns.select { |t| t[:amount].negative? }.each do |t|
41
- target = @wallets.find(t[:bnf])
38
+ def run(args = [])
39
+ opts = Slop.parse(args, help: true) do |o|
40
+ o.banner = "Usage: zold propagate [ID...] [options]
41
+ Available options:"
42
+ o.bool '--help', 'Print instructions'
43
+ end
44
+ if opts.help?
45
+ @log.info(opts.to_s)
46
+ return
47
+ end
48
+ raise 'At least one wallet ID is required' if opts.arguments.empty?
49
+ opts.arguments.each do |id|
50
+ propagate(@wallets.find(id), opts)
51
+ end
52
+ end
53
+
54
+ def propagate(wallet, _)
55
+ me = wallet.id
56
+ wallet.txns.select { |t| t.amount.negative? }.each do |t|
57
+ target = @wallets.find(t.bnf)
42
58
  unless target.exists?
43
- @log.debug("#{t[:amount].mul(-1)} to #{t[:bnf]}: wallet is absent")
59
+ @log.debug("#{t.amount.mul(-1)} to #{t.bnf}: wallet is absent")
60
+ next
61
+ end
62
+ next if target.has?(t.id, me)
63
+ unless Prefixes.new(target).valid?(t.prefix)
64
+ @log.info("#{t.amount.mul(-1)} to #{t.bnf}: wrong prefix")
44
65
  next
45
66
  end
46
- next if target.has?(t[:id], me)
47
- target.add(t)
48
- @log.info("#{t[:amount].mul(-1)} to #{t[:bnf]}")
67
+ target.add(t.inverse(me))
68
+ @log.info("#{t.amount.mul(-1)} to #{t.bnf}")
49
69
  end
50
- @log.debug('Wallet propagated successfully')
70
+ @log.debug("Wallet #{me} propagated successfully")
51
71
  end
52
72
  end
53
73
  end
@@ -18,9 +18,11 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
+ require 'slop'
21
22
  require 'net/http'
22
- require_relative '../log.rb'
23
- require_relative '../http.rb'
23
+ require_relative '../log'
24
+ require_relative '../id'
25
+ require_relative '../http'
24
26
 
25
27
  # PUSH command.
26
28
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -29,21 +31,37 @@ require_relative '../http.rb'
29
31
  module Zold
30
32
  # Wallet pushing command
31
33
  class Push
32
- def initialize(wallet:, remotes:, log: Log::Quiet.new)
33
- @wallet = wallet
34
+ def initialize(wallets:, remotes:, log: Log::Quiet.new)
35
+ @wallets = wallets
34
36
  @remotes = remotes
35
37
  @log = log
36
38
  end
37
39
 
38
- def run(_ = [])
39
- raise 'The wallet is absent' unless @wallet.exists?
40
+ def run(args = [])
41
+ opts = Slop.parse(args, help: true) do |o|
42
+ o.banner = "Usage: zold push [ID...] [options]
43
+ Available options:"
44
+ o.bool '--help', 'Print instructions'
45
+ end
46
+ if opts.help?
47
+ @log.info(opts.to_s)
48
+ return
49
+ end
50
+ raise 'At least one wallet ID is required' if opts.arguments.empty?
51
+ opts.arguments.each do |id|
52
+ push(@wallets.find(Id.new(id)), opts)
53
+ end
54
+ end
55
+
56
+ def push(wallet, _)
57
+ raise 'The wallet is absent' unless wallet.exists?
40
58
  remote = @remotes.all[0]
41
- uri = URI("#{remote[:home]}/wallet/#{@wallet.id}")
42
- response = Http.new(uri).put(File.read(@wallet.path))
59
+ uri = URI("#{remote[:home]}/wallet/#{wallet.id}")
60
+ response = Http.new(uri).put(File.read(wallet.path))
43
61
  unless response.code == '200'
44
62
  raise "Failed to push to #{uri}: #{response.code}/#{response.message}"
45
63
  end
46
- @log.info("The #{@wallet.id} pushed to #{uri}")
64
+ @log.info("The #{wallet.id} pushed to #{uri}")
47
65
  end
48
66
  end
49
67
  end
@@ -18,14 +18,15 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
+ require 'slop'
21
22
  require 'rainbow'
22
23
  require 'net/http'
23
24
  require 'json'
24
25
  require 'time'
25
- require_relative '../log.rb'
26
- require_relative '../http.rb'
27
- require_relative '../remotes.rb'
28
- require_relative '../score.rb'
26
+ require_relative '../log'
27
+ require_relative '../http'
28
+ require_relative '../remotes'
29
+ require_relative '../score'
29
30
 
30
31
  # REMOTE command.
31
32
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -56,48 +57,60 @@ Available commands:
56
57
  #{Rainbow('remote update').green}
57
58
  Check each registered remote node for availability
58
59
  Available options:"
60
+ o.bool '--ignore-score-weakness',
61
+ 'Don\'t complain when their score is too weak',
62
+ default: false
59
63
  o.bool '--help', 'Print instructions'
60
64
  end
61
65
  command = args[0]
62
66
  case command
63
67
  when 'show'
64
- @remotes.all.each do |r|
65
- score = Rainbow("/#{r[:score]}").color(r[:score] > 0 ? :green : :red)
66
- @log.info(r[:host] + Rainbow(":#{r[:port]}").gray + score)
67
- end
68
+ show
68
69
  when 'clean'
69
- @remotes.clean
70
- @log.debug('All remote nodes deleted')
70
+ clean
71
71
  when 'reset'
72
- @remotes.reset
73
- @log.debug('Remote nodes set back to default')
72
+ reset
74
73
  when 'add'
75
- host = args[1]
76
- port = args[2]
77
- @remotes.add(host, port)
78
- @log.info("#{host}:#{port}: added")
74
+ add(opts.arguments[1], opts.arguments[2].to_i)
79
75
  when 'remove'
80
- host = args[1]
81
- port = args[2]
82
- @remotes.remove(host, port)
83
- @log.info("#{host}:#{port}: removed")
76
+ remove(opts.arguments[1], opts.arguments[2].to_i)
84
77
  when 'update'
85
- update
86
- total = @remotes.all.size
87
- if total.zero?
88
- @log.debug("The list of remotes is #{Rainbow('empty').red}!")
89
- @log.debug(
90
- "Run 'zold remote add b1.zold.io 80` and then `zold update`"
91
- )
92
- else
93
- @log.debug("There are #{total} known remotes")
94
- end
78
+ update(opts)
95
79
  else
96
80
  @log.info(opts.to_s)
97
81
  end
98
82
  end
99
83
 
100
- def update
84
+ def show
85
+ @remotes.all.each do |r|
86
+ score = Rainbow("/#{r[:score]}").color(r[:score] > 0 ? :green : :red)
87
+ @log.info(r[:host] + Rainbow(":#{r[:port]}").gray + score)
88
+ end
89
+ end
90
+
91
+ def clean
92
+ @remotes.clean
93
+ @log.debug('All remote nodes deleted')
94
+ end
95
+
96
+ def reset
97
+ @remotes.reset
98
+ @log.debug('Remote nodes set back to default')
99
+ end
100
+
101
+ def add(host, port)
102
+ @remotes.add(host, port)
103
+ @log.info("#{host}:#{port} added to the list")
104
+ @log.info("There are #{@remotes.all.count} remote nodes in the list")
105
+ end
106
+
107
+ def remove(host, port)
108
+ @remotes.remove(host, port)
109
+ @log.info("#{host}:#{port} removed from the list")
110
+ @log.info("There are #{@remotes.all.count} remote nodes in the list")
111
+ end
112
+
113
+ def update(opts)
101
114
  @remotes.all.each do |r|
102
115
  uri = URI("#{r[:home]}remotes")
103
116
  res = Http.new(uri).get
@@ -111,7 +124,7 @@ Available options:"
111
124
  begin
112
125
  json = JSON.parse(res.body)
113
126
  rescue JSON::ParserError => e
114
- @remotes.remove(r[:host], r[:port])
127
+ remove(r[:host], r[:port])
115
128
  @log.info("#{Rainbow(r[:host]).red} \"#{e.message}\": #{res.body}")
116
129
  next
117
130
  end
@@ -120,19 +133,35 @@ Available options:"
120
133
  r[:port], json['score']['suffixes']
121
134
  )
122
135
  unless score.valid?
123
- @remotes.remove(r[:host], r[:port])
136
+ remove(r[:host], r[:port])
124
137
  @log.info("#{Rainbow(r[:host]).red} invalid score")
125
138
  next
126
139
  end
140
+ if score.strength < Score::STRENGTH && !opts['ignore-score-weakness']
141
+ remove(r[:host], r[:port])
142
+ @log.info(
143
+ "#{Rainbow(r[:host]).red} score too weak: #{score.strength}"
144
+ )
145
+ next
146
+ end
127
147
  @remotes.rescore(r[:host], r[:port], score.value)
128
148
  json['all'].each do |s|
129
149
  unless @remotes.exists?(s['host'], s['port'])
130
- run(['add', s['host'], s['port'].to_s])
150
+ add(s['host'], s['port'])
131
151
  end
132
152
  end
133
153
  @log.info("#{r[:host]}:#{r[:port]}: #{Rainbow(score.value).green} \
134
154
  (v.#{json['version']})")
135
155
  end
156
+ total = @remotes.all.size
157
+ if total.zero?
158
+ @log.debug("The list of remotes is #{Rainbow('empty').red}!")
159
+ @log.debug(
160
+ "Run 'zold remote add b1.zold.io 80` and then `zold update`"
161
+ )
162
+ else
163
+ @log.debug("There are #{total} known remotes")
164
+ end
136
165
  end
137
166
  end
138
167
  end
@@ -18,7 +18,10 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- require_relative '../log.rb'
21
+ require 'slop'
22
+ require_relative '../log'
23
+ require_relative '../id'
24
+ require_relative '../amount'
22
25
 
23
26
  # SHOW command.
24
27
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -27,18 +30,40 @@ require_relative '../log.rb'
27
30
  module Zold
28
31
  # Show command
29
32
  class Show
30
- def initialize(wallet:, log: Log::Quiet.new)
31
- @wallet = wallet
33
+ def initialize(wallets:, log: Log::Quiet.new)
34
+ @wallets = wallets
32
35
  @log = log
33
36
  end
34
37
 
35
- def run(_ = [])
36
- balance = @wallet.balance
37
- @log.debug("The balance of #{@wallet} is #{balance}:")
38
- @wallet.txns.each do |t|
39
- @log.info("##{t[:id]} #{t[:date].utc.iso8601} \
40
- #{t[:amount]} #{t[:bnf]} #{t[:details]}")
38
+ def run(args = [])
39
+ opts = Slop.parse(args, help: true) do |o|
40
+ o.banner = "Usage: zold show [ID...] [options]
41
+ Available options:"
42
+ o.bool '--help', 'Print instructions'
41
43
  end
44
+ if opts.help?
45
+ @log.info(opts.to_s)
46
+ return
47
+ end
48
+ if opts.arguments.empty?
49
+ require_relative 'list'
50
+ List.new(wallets: @wallets, log: @log).run(args)
51
+ else
52
+ total = Amount::ZERO
53
+ opts.arguments.each do |id|
54
+ total += show(@wallets.find(Id.new(id)), opts)
55
+ end
56
+ total
57
+ end
58
+ end
59
+
60
+ def show(wallet, _)
61
+ balance = wallet.balance
62
+ wallet.txns.each do |t|
63
+ @log.info("##{t.id} #{t.date.utc.iso8601} \
64
+ #{t.amount} #{t.bnf} #{t.details}")
65
+ end
66
+ @log.info("The balance of #{wallet}: #{balance}")
42
67
  balance
43
68
  end
44
69
  end
data/lib/zold/copies.rb CHANGED
@@ -32,6 +32,10 @@ module Zold
32
32
  @dir = dir
33
33
  end
34
34
 
35
+ def root
36
+ File.join(@dir, '..')
37
+ end
38
+
35
39
  def to_s
36
40
  File.basename(@dir)
37
41
  end
data/lib/zold/http.rb CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  require 'rainbow'
22
22
  require 'net/http'
23
- require_relative 'score.rb'
23
+ require_relative 'score'
24
24
 
25
25
  # HTTP page.
26
26
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
data/lib/zold/key.rb CHANGED
@@ -29,17 +29,23 @@ module Zold
29
29
  # A key
30
30
  class Key
31
31
  def initialize(file: nil, text: nil)
32
- unless file.nil?
33
- path = File.expand_path(file)
34
- raise "Can't find RSA key at #{file} (#{path})" unless File.exist?(path)
35
- @body = File.read(path)
32
+ @body = lambda do
33
+ unless file.nil?
34
+ path = File.expand_path(file)
35
+ unless File.exist?(path)
36
+ raise "Can't find RSA key at #{file} (#{path})"
37
+ end
38
+ return File.read(path)
39
+ end
40
+ unless text.nil?
41
+ return [
42
+ '-----BEGIN PUBLIC KEY-----',
43
+ text.gsub(/(?<=\G.{64})/, "\n"),
44
+ '-----END PUBLIC KEY-----'
45
+ ].join("\n")
46
+ end
47
+ raise 'Either file or text must be set'
36
48
  end
37
- return if text.nil?
38
- @body = [
39
- '-----BEGIN PUBLIC KEY-----',
40
- text.gsub(/(?<=\G.{64})/, "\n"),
41
- '-----END PUBLIC KEY-----'
42
- ].join("\n")
43
49
  end
44
50
 
45
51
  def to_s
@@ -61,7 +67,7 @@ module Zold
61
67
  private
62
68
 
63
69
  def rsa
64
- text = @body.strip
70
+ text = @body.call.strip
65
71
  unless text.start_with?('-----BEGIN')
66
72
  text = OpenSSHKeyConverter.decode_pubkey(text.split[1])
67
73
  end
@@ -39,6 +39,14 @@ module Zold
39
39
  @semaphore = Mutex.new
40
40
  end
41
41
 
42
+ def to_json
43
+ {
44
+ threads: @threads.count,
45
+ scores: @scores.size,
46
+ best: @best.count
47
+ }
48
+ end
49
+
42
50
  def start(host, port, strength: 8, threads: 8)
43
51
  @log.debug('Zero-threads farm won\'t score anything!') if threads.zero?
44
52
  @scores = Queue.new
@@ -47,8 +47,9 @@ module Zold
47
47
  configure do
48
48
  set :bind, '0.0.0.0'
49
49
  set :logging, true
50
+ set :dump_errors, true
50
51
  set :start, Time.now
51
- set :lock, Mutex.new
52
+ set :lock, true
52
53
  set :log, Log.new
53
54
  set :show_exceptions, false
54
55
  set :home, Dir.pwd
@@ -59,7 +60,9 @@ module Zold
59
60
  before do
60
61
  if request.env[Http::SCORE_HEADER]
61
62
  s = Score.parse(request.env[Http::SCORE_HEADER])
62
- raise 'The score is invalid' if !s.valid? || s.value < 3
63
+ error(400, 'The score is invalid') unless s.valid?
64
+ error(400, 'The score is too small') if s.value < 3
65
+ error(400, 'The score is weak') if s.strength < Score::STRENGTH
63
66
  settings.remotes.add(s.host, s.port)
64
67
  end
65
68
  end
@@ -93,6 +96,7 @@ module Zold
93
96
  wallets: {
94
97
  total: wallets.all.count
95
98
  },
99
+ farm: settings.farm.to_json,
96
100
  date: `date --iso-8601=seconds -u`.strip,
97
101
  age: Time.now - settings.start,
98
102
  home: 'https://www.zold.io'
@@ -117,7 +121,7 @@ module Zold
117
121
  request.body.rewind
118
122
  cps = copies(id)
119
123
  cps.add(request.body.read, 'remote', Remotes::PORT, 0)
120
- Zold::Merge.new(wallet: wallet, copies: cps).run
124
+ Zold::Merge.new(wallets: wallets, copies: cps.root).run([id.to_s])
121
125
  "Success, #{wallet.id} balance is #{wallet.balance}"
122
126
  end
123
127
 
@@ -141,6 +145,12 @@ module Zold
141
145
  'Page not found'
142
146
  end
143
147
 
148
+ error 400 do
149
+ status 400
150
+ content_type 'text/plain'
151
+ env['sinatra.error'].message
152
+ end
153
+
144
154
  error do
145
155
  status 503
146
156
  e = env['sinatra.error']
data/lib/zold/patch.rb CHANGED
@@ -18,7 +18,7 @@
18
18
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
19
  # SOFTWARE.
20
20
 
21
- require_relative 'wallet.rb'
21
+ require_relative 'wallet'
22
22
 
23
23
  # Patch.
24
24
  #
@@ -35,15 +35,15 @@ module Zold
35
35
  end
36
36
 
37
37
  def join(wallet)
38
- negative = @txns.select { |t| t[:amount].negative? }
39
- max = negative.empty? ? 0 : negative.max_by { |t| t[:id] }[:id]
38
+ negative = @txns.select { |t| t.amount.negative? }
39
+ max = negative.empty? ? 0 : negative.max_by(&:id).id
40
40
  wallet.txns.each do |txn|
41
- next if @txns.find { |t| t[:id] == txn[:id] && t[:bnf] == txn[:bnf] }
41
+ next if @txns.find { |t| t == txn }
42
42
  next if
43
- txn[:amount].negative? && !@txns.empty? &&
44
- (txn[:id] <= max ||
45
- @txns.find { |t| t[:id] == txn[:id] } ||
46
- @txns.map { |t| t[:amount] }.inject(&:+) < txn[:amount])
43
+ txn.amount.negative? && !@txns.empty? &&
44
+ (txn.id <= max ||
45
+ @txns.find { |t| t.id == txn.id } ||
46
+ @txns.map(&:amount).inject(&:+) < txn.amount)
47
47
  next unless Signature.new.valid?(@key, txn)
48
48
  @txns << txn
49
49
  end
@@ -0,0 +1,53 @@
1
+ # Copyright (c) 2018 Yegor Bugayenko
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the 'Software'), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in all
11
+ # copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ # SOFTWARE.
20
+
21
+ require_relative 'key'
22
+
23
+ # Payment prefixes.
24
+ #
25
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
27
+ # License:: MIT
28
+ module Zold
29
+ # Payment prefixes
30
+ class Prefixes
31
+ def initialize(wallet)
32
+ @wallet = wallet
33
+ end
34
+
35
+ def create(length)
36
+ raise "Length #{length} is too small" if length < 8
37
+ raise "Length #{length} is too big" if length > 32
38
+ key = body
39
+ start = Random.new.rand(key.length - length)
40
+ key[start..(start + length - 1)]
41
+ end
42
+
43
+ def valid?(prefix)
44
+ body.include?(prefix)
45
+ end
46
+
47
+ private
48
+
49
+ def body
50
+ @wallet.key.to_pub.gsub(/[^A-Z0-9a-z]/, '')
51
+ end
52
+ end
53
+ end
data/lib/zold/remotes.rb CHANGED
@@ -60,6 +60,9 @@ module Zold
60
60
  end
61
61
 
62
62
  def add(host, port = Remotes::PORT)
63
+ raise 'Port has to be of type Integer' unless port.is_a?(Integer)
64
+ raise 'Port can\'t be negative' if port < 0
65
+ raise 'Port can\'t be over 65536' if port > 0xffff
63
66
  list = load
64
67
  list << { host: host, port: port, score: 0 }
65
68
  list.uniq! { |r| "#{r[:host]}:#{r[:port]}" }
@@ -67,16 +70,19 @@ module Zold
67
70
  end
68
71
 
69
72
  def remove(host, port = Remotes::PORT)
73
+ raise 'Port has to be of type Integer' unless port.is_a?(Integer)
70
74
  list = load
71
75
  list.reject! { |r| r[:host] == host && r[:port] == port }
72
76
  save(list)
73
77
  end
74
78
 
75
79
  def score(host, port = Remotes::PORT)
80
+ raise 'Port has to be of type Integer' unless port.is_a?(Integer)
76
81
  load.find { |r| r[:host] == host && r[:port] == port }[:score]
77
82
  end
78
83
 
79
84
  def rescore(host, port, score)
85
+ raise 'Port has to be of type Integer' unless port.is_a?(Integer)
80
86
  list = load
81
87
  list.find do |r|
82
88
  r[:host] == host && r[:port] == port