zold 0.0.8 → 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE.md +12 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  4. data/.rubocop.yml +9 -1
  5. data/.simplecov +2 -2
  6. data/.travis.yml +1 -1
  7. data/Gemfile +1 -1
  8. data/LICENSE.txt +1 -1
  9. data/Procfile +1 -1
  10. data/README.md +208 -101
  11. data/Rakefile +2 -1
  12. data/bin/zold +135 -54
  13. data/features/cli.feature +1 -1
  14. data/features/step_definitions/steps.rb +1 -1
  15. data/features/support/env.rb +1 -1
  16. data/fixtures/scripts/push-and-pull.sh +35 -0
  17. data/lib/zold.rb +2 -2
  18. data/lib/zold/amount.rb +10 -2
  19. data/lib/zold/commands/{send.rb → clean.rb} +16 -16
  20. data/lib/zold/commands/create.rb +7 -5
  21. data/lib/zold/commands/diff.rb +59 -0
  22. data/lib/zold/commands/fetch.rb +74 -0
  23. data/lib/zold/commands/{pull.rb → list.rb} +11 -17
  24. data/lib/zold/commands/merge.rb +50 -0
  25. data/lib/zold/commands/node.rb +94 -0
  26. data/lib/zold/commands/pay.rb +58 -0
  27. data/lib/zold/commands/{check.rb → propagate.rb} +19 -20
  28. data/lib/zold/commands/push.rb +12 -12
  29. data/lib/zold/commands/remote.rb +115 -0
  30. data/lib/zold/commands/{balance.rb → show.rb} +11 -7
  31. data/lib/zold/copies.rb +126 -0
  32. data/lib/zold/http.rb +70 -0
  33. data/lib/zold/id.rb +8 -3
  34. data/lib/zold/key.rb +2 -2
  35. data/lib/zold/log.rb +51 -2
  36. data/lib/zold/node/farm.rb +81 -0
  37. data/lib/zold/node/front.rb +94 -46
  38. data/lib/zold/patch.rb +58 -0
  39. data/lib/zold/remotes.rb +106 -0
  40. data/lib/zold/score.rb +101 -0
  41. data/lib/zold/signature.rb +48 -0
  42. data/lib/zold/version.rb +3 -3
  43. data/lib/zold/wallet.rb +87 -55
  44. data/lib/zold/wallets.rb +13 -6
  45. data/resources/remotes +1 -0
  46. data/test/commands/test_clean.rb +41 -0
  47. data/test/commands/test_create.rb +2 -2
  48. data/test/commands/test_diff.rb +61 -0
  49. data/test/commands/test_fetch.rb +65 -0
  50. data/test/commands/test_list.rb +42 -0
  51. data/test/commands/test_merge.rb +62 -0
  52. data/test/commands/test_node.rb +56 -0
  53. data/test/commands/{test_send.rb → test_pay.rb} +10 -11
  54. data/test/commands/test_remote.rb +60 -0
  55. data/test/commands/{test_balance.rb → test_show.rb} +6 -8
  56. data/test/node/fake_node.rb +73 -0
  57. data/test/node/test_farm.rb +34 -0
  58. data/test/node/test_front.rb +26 -57
  59. data/test/test__helper.rb +1 -1
  60. data/test/test_amount.rb +10 -2
  61. data/test/test_copies.rb +73 -0
  62. data/test/test_http.rb +42 -0
  63. data/test/test_id.rb +2 -2
  64. data/test/test_key.rb +10 -10
  65. data/test/test_patch.rb +59 -0
  66. data/test/test_remotes.rb +72 -0
  67. data/test/test_score.rb +79 -0
  68. data/test/test_signature.rb +45 -0
  69. data/test/test_wallet.rb +18 -35
  70. data/test/test_wallets.rb +14 -3
  71. data/test/test_zold.rb +52 -5
  72. data/zold.gemspec +5 -3
  73. metadata +92 -21
  74. data/CONTRIBUTING.md +0 -19
  75. data/views/index.haml +0 -6
  76. data/views/layout.haml +0 -26
  77. data/views/not_found.haml +0 -3
@@ -0,0 +1,115 @@
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 'rainbow'
22
+ require 'net/http'
23
+ require 'json'
24
+ require 'time'
25
+ require_relative '../log.rb'
26
+ require_relative '../http.rb'
27
+ require_relative '../remotes.rb'
28
+ require_relative '../score.rb'
29
+
30
+ # REMOTE command.
31
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
32
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
33
+ # License:: MIT
34
+ module Zold
35
+ # Remote command
36
+ class Remote
37
+ def initialize(remotes:, log: Log::Quiet.new)
38
+ @remotes = remotes
39
+ @log = log
40
+ end
41
+
42
+ def run(args = [])
43
+ command = args[0]
44
+ case command
45
+ when 'show'
46
+ @remotes.all.each do |r|
47
+ score = Rainbow("/#{r[:score]}").color(r[:score] > 0 ? :green : :red)
48
+ @log.info(r[:host] + Rainbow(":#{r[:port]}").gray + score)
49
+ end
50
+ when 'clean'
51
+ @remotes.clean
52
+ @log.debug('All remote nodes deleted')
53
+ when 'reset'
54
+ @remotes.reset
55
+ @log.debug('Remote nodes set back to default')
56
+ when 'add'
57
+ host = args[1]
58
+ port = args[2]
59
+ @remotes.add(host, port)
60
+ @log.info("#{host}:#{port}: added")
61
+ when 'remove'
62
+ host = args[1]
63
+ port = args[2]
64
+ @remotes.remove(host, port)
65
+ @log.info("#{host}:#{port}: removed")
66
+ when 'update'
67
+ update
68
+ total = @remotes.all.size
69
+ if total.zero?
70
+ @log.debug("The list of remotes is #{Rainbow('empty').red}!")
71
+ @log.debug(
72
+ "Run 'zold remote add b1.zold.io 80` and then `zold update`"
73
+ )
74
+ else
75
+ @log.debug("There are #{total} known remotes")
76
+ end
77
+ else
78
+ raise "Command '#{command}' is not supported"
79
+ end
80
+ end
81
+
82
+ def update
83
+ @remotes.all.each do |r|
84
+ uri = URI("#{r[:home]}remotes")
85
+ res = Http.new(uri).get
86
+ unless res.code == '200'
87
+ @remotes.remove(r[:host], r[:port])
88
+ @log.info(
89
+ "#{Rainbow(r[:host]).red} #{res.code} \"#{res.message}\" #{uri}"
90
+ )
91
+ next
92
+ end
93
+ begin
94
+ json = JSON.parse(res.body)
95
+ rescue JSON::ParserError => e
96
+ @remotes.remove(r[:host], r[:port])
97
+ @log.info("#{Rainbow(r[:host]).red} \"#{e.message}\": #{res.body}")
98
+ next
99
+ end
100
+ score = Score.new(
101
+ Time.parse(json['score']['time']), r[:host],
102
+ r[:port], json['score']['suffixes']
103
+ )
104
+ unless score.valid?
105
+ @remotes.remove(r[:host], r[:port])
106
+ @log.info("#{Rainbow(r[:host]).red} invalid score")
107
+ next
108
+ end
109
+ @remotes.rescore(r[:host], r[:port], score.value)
110
+ json['all'].each { |s| run(['add', s['host'], s['port']]) }
111
+ @log.info("#{r[:host]}:#{r[:port]}: #{Rainbow(score.value).green}")
112
+ end
113
+ end
114
+ end
115
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018 Zerocracy, Inc.
1
+ # Copyright (c) 2018 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -20,21 +20,25 @@
20
20
 
21
21
  require_relative '../log.rb'
22
22
 
23
- # BALANCE command.
23
+ # SHOW command.
24
24
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
- # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
25
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
26
  # License:: MIT
27
27
  module Zold
28
- # Balance checking command
29
- class Balance
28
+ # Show command
29
+ class Show
30
30
  def initialize(wallet:, log: Log::Quiet.new)
31
31
  @wallet = wallet
32
32
  @log = log
33
33
  end
34
34
 
35
- def run
35
+ def run(_ = [])
36
36
  balance = @wallet.balance
37
- @log.info("The balance of #{@wallet} is #{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]}")
41
+ end
38
42
  balance
39
43
  end
40
44
  end
@@ -0,0 +1,126 @@
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 'time'
22
+ require 'csv'
23
+
24
+ # The list of copies.
25
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
27
+ # License:: MIT
28
+ module Zold
29
+ # All copies
30
+ class Copies
31
+ def initialize(dir)
32
+ @dir = dir
33
+ end
34
+
35
+ def to_s
36
+ File.basename(@dir)
37
+ end
38
+
39
+ def clean
40
+ list = load
41
+ list.reject! { |s| s[:time] < Time.now - 24 * 60 }
42
+ save(list)
43
+ Dir.new(@dir).select { |f| f =~ /[0-9]+/ }.each do |f|
44
+ File.delete(File.join(@dir, f)) if list.find { |s| s[:name] == f }.nil?
45
+ end
46
+ end
47
+
48
+ def add(content, host, port, score, time = Time.now)
49
+ raise "Content can't be empty" if content.empty?
50
+ raise 'TCP port must be of type Integer' unless port.is_a?(Integer)
51
+ raise "TCP port can't be negative: #{port}" if port < 0
52
+ raise 'Time must be of type Time' unless time.is_a?(Time)
53
+ raise "Time must be in the past: #{time}" if time > Time.now
54
+ raise 'Score must be Integer' unless score.is_a?(Integer)
55
+ raise "Score can't be negative: #{score}" if score < 0
56
+ FileUtils.mkdir_p(@dir)
57
+ list = load
58
+ target = list.find { |s| File.read(File.join(@dir, s[:name])) == content }
59
+ if target.nil?
60
+ max = Dir.new(@dir)
61
+ .select { |f| f =~ /[0-9]+/ }
62
+ .map(&:to_i)
63
+ .max
64
+ max = 0 if max.nil?
65
+ name = (max + 1).to_s
66
+ File.write(File.join(@dir, name), content)
67
+ else
68
+ name = target[:name]
69
+ end
70
+ list.reject! { |s| s[:host] == host && s[:port] == port }
71
+ list << {
72
+ name: name,
73
+ host: host,
74
+ port: port,
75
+ score: score,
76
+ time: time
77
+ }
78
+ save(list)
79
+ end
80
+
81
+ def all
82
+ load.group_by { |s| s[:name] }.map do |name, scores|
83
+ {
84
+ name: name,
85
+ path: File.join(@dir, name),
86
+ score: scores.select { |s| s[:time] > Time.now - 24 * 60 }
87
+ .map { |s| s[:score] }
88
+ .inject(&:+)
89
+ }
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def load
96
+ FileUtils.mkdir_p(File.dirname(file))
97
+ FileUtils.touch(file)
98
+ CSV.read(file).map do |s|
99
+ {
100
+ name: s[0],
101
+ host: s[1],
102
+ port: s[2].to_i,
103
+ score: s[3].to_i,
104
+ time: Time.parse(s[4])
105
+ }
106
+ end
107
+ end
108
+
109
+ def save(list)
110
+ File.write(
111
+ file,
112
+ list.map do |r|
113
+ [
114
+ r[:name], r[:host],
115
+ r[:port], r[:score],
116
+ r[:time].utc.iso8601
117
+ ].join(',')
118
+ end.join("\n")
119
+ )
120
+ end
121
+
122
+ def file
123
+ File.join(@dir, 'scores')
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,70 @@
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 'rainbow'
22
+ require 'net/http'
23
+ require_relative 'score.rb'
24
+
25
+ # HTTP page.
26
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
27
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
28
+ # License:: MIT
29
+ module Zold
30
+ # Http page
31
+ class Http
32
+ SCORE_HEADER = 'X-Zold-Score'.freeze
33
+
34
+ def initialize(uri, score = Score::ZERO)
35
+ @uri = uri
36
+ @score = score
37
+ end
38
+
39
+ def get
40
+ http = Net::HTTP.new(@uri.host, @uri.port)
41
+ http.read_timeout = 5
42
+ http.continue_timeout = 5
43
+ return http.request_get(@uri.path, headers)
44
+ rescue StandardError => e
45
+ return Net::HTTPServerError.new('1.1', '599', e.message)
46
+ end
47
+
48
+ def put(body)
49
+ http = Net::HTTP.new(@uri.host, @uri.port)
50
+ http.read_timeout = 5
51
+ http.continue_timeout = 5
52
+ return http.request_put(
53
+ @uri.path, body,
54
+ headers.merge('Content-Type': 'text/plain')
55
+ )
56
+ rescue StandardError => e
57
+ return Net::HTTPServerError.new('1.1', '599', e.message)
58
+ end
59
+
60
+ private
61
+
62
+ def headers
63
+ headers = {
64
+ 'User-Agent': 'Zold'
65
+ }
66
+ headers[SCORE_HEADER] = score.to_s if @score.valid? && @score.value >= 3
67
+ headers
68
+ end
69
+ end
70
+ end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018 Zerocracy, Inc.
1
+ # Copyright (c) 2018 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -20,13 +20,18 @@
20
20
 
21
21
  # The ID of the wallet.
22
22
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
23
- # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
23
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
24
24
  # License:: MIT
25
25
  module Zold
26
26
  # Id of the wallet
27
27
  class Id
28
28
  def initialize(id = nil)
29
- @id = id.nil? ? rand(2**32..2**64 - 1) : Integer("0x#{id}", 16)
29
+ if id.nil?
30
+ @id = rand(2**32..2**64 - 1)
31
+ else
32
+ raise "Invalid wallet ID '#{id}'" unless id =~ /[0-9a-fA-F]{16}/
33
+ @id = Integer("0x#{id}", 16)
34
+ end
30
35
  end
31
36
 
32
37
  ROOT = Id.new('0000000000000000')
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018 Zerocracy, Inc.
1
+ # Copyright (c) 2018 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -23,7 +23,7 @@ require 'base64'
23
23
 
24
24
  # The RSA key (either private or public).
25
25
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
26
- # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
26
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
27
27
  # License:: MIT
28
28
  module Zold
29
29
  # A key
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018 Zerocracy, Inc.
1
+ # Copyright (c) 2018 Yegor Bugayenko
2
2
  #
3
3
  # Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  # of this software and associated documentation files (the 'Software'), to deal
@@ -20,27 +20,76 @@
20
20
 
21
21
  require 'rainbow'
22
22
 
23
+ STDOUT.sync = true
24
+
23
25
  # The log.
24
26
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
25
- # Copyright:: Copyright (c) 2018 Zerocracy, Inc.
27
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
28
  # License:: MIT
27
29
  module Zold
28
30
  # Logging
29
31
  class Log
32
+ def debug(msg)
33
+ # nothing
34
+ end
35
+
36
+ def debug?
37
+ false
38
+ end
39
+
30
40
  def info(msg)
31
41
  puts msg
32
42
  end
33
43
 
44
+ def info?
45
+ true
46
+ end
47
+
34
48
  def error(msg)
35
49
  puts "#{Rainbow('ERROR').red}: #{msg}"
36
50
  end
37
51
 
52
+ # Extra verbose log
53
+ class Verbose
54
+ def debug(msg)
55
+ puts msg
56
+ end
57
+
58
+ def debug?
59
+ true
60
+ end
61
+
62
+ def info(msg)
63
+ puts msg
64
+ end
65
+
66
+ def info?
67
+ true
68
+ end
69
+
70
+ def error(msg)
71
+ puts "#{Rainbow('ERROR').red}: #{msg}"
72
+ end
73
+ end
74
+
38
75
  # Log that doesn't log anything
39
76
  class Quiet
77
+ def debug(msg)
78
+ # nothing to do here
79
+ end
80
+
81
+ def debug?
82
+ false
83
+ end
84
+
40
85
  def info(msg)
41
86
  # nothing to do here
42
87
  end
43
88
 
89
+ def info?
90
+ false
91
+ end
92
+
44
93
  def error(msg)
45
94
  # nothing to do here
46
95
  end