zold 0.2 → 0.3

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