zold 0.13.46 → 0.14.0
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 +4 -4
- data/.ruby-version +1 -0
- data/.rultor.yml +2 -2
- data/.simplecov +1 -1
- data/.travis.yml +3 -1
- data/INSTALL.md +4 -1
- data/README.md +98 -3
- data/Rakefile +1 -0
- data/bin/zold +10 -1
- data/fixtures/merge/into-no-wallet/assert.rb +23 -0
- data/fixtures/merge/{random-expenses/0123456789abcdef → into-no-wallet/copies/0123456789abcdef/1.z} +0 -0
- data/fixtures/merge/into-no-wallet/copies/0123456789abcdef/scores.z +1 -0
- data/fixtures/merge/random-expenses/{0000000000000000 → 0000000000000000.z} +0 -0
- data/fixtures/merge/{simple-case/copies/0123456789abcdef/1 → random-expenses/0123456789abcdef.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/{1 → 1.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/{2 → 2.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/{3 → 3.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/{4 → 4.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/{5 → 5.z} +0 -0
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/scores.z +5 -0
- data/fixtures/merge/simple-case/{0000000000000000 → 0000000000000000.z} +0 -0
- data/fixtures/merge/simple-case/{0123456789abcdef → 0123456789abcdef.z} +0 -1
- data/fixtures/merge/simple-case/copies/0123456789abcdef/1.z +6 -0
- data/fixtures/merge/simple-case/copies/0123456789abcdef/scores.z +1 -0
- data/fixtures/scripts/push-and-pull.sh +1 -0
- data/lib/zold/atomic_file.rb +7 -6
- data/lib/zold/commands/alias.rb +37 -0
- data/lib/zold/commands/calculate.rb +5 -2
- data/lib/zold/commands/diff.rb +2 -2
- data/lib/zold/commands/fetch.rb +23 -3
- data/lib/zold/commands/merge.rb +12 -7
- data/lib/zold/commands/node.rb +5 -2
- data/lib/zold/commands/pay.rb +11 -0
- data/lib/zold/commands/push.rb +1 -1
- data/lib/zold/commands/remote.rb +28 -2
- data/lib/zold/commands/taxes.rb +14 -2
- data/lib/zold/copies.rb +9 -8
- data/lib/zold/hungry_wallets.rb +12 -0
- data/lib/zold/log.rb +17 -2
- data/lib/zold/node/async_entrance.rb +48 -9
- data/lib/zold/node/entrance.rb +1 -1
- data/lib/zold/node/front.rb +28 -3
- data/lib/zold/node/safe_entrance.rb +3 -3
- data/lib/zold/node/spread_entrance.rb +1 -1
- data/lib/zold/patch.rb +8 -4
- data/lib/zold/remotes.rb +7 -0
- data/lib/zold/score.rb +2 -2
- data/lib/zold/tax.rb +0 -1
- data/lib/zold/upgrades.rb +57 -0
- data/lib/zold/version.rb +2 -1
- data/lib/zold/version_file.rb +39 -0
- data/lib/zold/wallet.rb +17 -4
- data/lib/zold/wallets.rb +9 -6
- data/test/commands/routines/test_spread.rb +1 -0
- data/test/commands/test_alias.rb +7 -0
- data/test/commands/test_create.rb +2 -2
- data/test/commands/test_fetch.rb +2 -1
- data/test/commands/test_merge.rb +2 -0
- data/test/commands/test_node.rb +1 -0
- data/test/commands/test_pay.rb +24 -0
- data/test/commands/test_remote.rb +28 -0
- data/test/node/test_async_entrance.rb +6 -13
- data/test/node/test_front.rb +2 -1
- data/test/test__helper.rb +3 -2
- data/test/test_atomic_file.rb +0 -5
- data/test/test_copies.rb +14 -2
- data/test/test_remotes.rb +12 -0
- data/test/test_score.rb +14 -0
- data/test/test_tax.rb +5 -0
- data/test/test_upgrades.rb +97 -0
- data/test/test_version.rb +13 -0
- data/test/test_wallet.rb +21 -1
- data/test/test_wallets.rb +8 -0
- data/upgrades/2.rb +20 -0
- data/zold.gemspec +3 -3
- metadata +38 -23
- data/fixtures/merge/random-expenses/copies/0123456789abcdef/scores +0 -5
- data/fixtures/merge/simple-case/copies/0123456789abcdef/scores +0 -1
data/lib/zold/commands/remote.rb
CHANGED
@@ -63,10 +63,15 @@ Available commands:
|
|
63
63
|
#{Rainbow('remote elect').green}
|
64
64
|
Pick a random remote node as a target for a bonus awarding
|
65
65
|
#{Rainbow('remote trim').green}
|
66
|
-
|
66
|
+
Remove the least reliable nodes
|
67
|
+
#{Rainbow('remote select [options]').green}
|
68
|
+
Select the strongest n nodes.
|
67
69
|
#{Rainbow('remote update').green}
|
68
70
|
Check each registered remote node for availability
|
69
71
|
Available options:"
|
72
|
+
o.integer '--tolerate',
|
73
|
+
'Maximum level of errors we are able to tolerate',
|
74
|
+
default: Remotes::TOLERANCE
|
70
75
|
o.bool '--ignore-score-weakness',
|
71
76
|
'Don\'t complain when their score is too weak',
|
72
77
|
default: false
|
@@ -86,6 +91,19 @@ Available options:"
|
|
86
91
|
o.bool '--reboot',
|
87
92
|
'Exit if any node reports version higher than we have',
|
88
93
|
default: false
|
94
|
+
|
95
|
+
# @todo #292:30min Group options by subcommands
|
96
|
+
# Having all the options in one place _rather than grouping them by subcommands_
|
97
|
+
# makes the help totally misleading and hard to read.
|
98
|
+
# Not all the options are valid for every command - that's the key here.
|
99
|
+
# The option below (`--max-nodes`) is an example.
|
100
|
+
# **Next actions:**
|
101
|
+
# - Implement the suggestion above.
|
102
|
+
# - Remove note from the --max-nodes option saying that it applies to the select
|
103
|
+
# subcommand only.
|
104
|
+
o.integer '--max-nodes',
|
105
|
+
"This applies only to the select subcommand. Number of nodes to limit to. Defaults to #{Remotes::MAX_NODES}.",
|
106
|
+
default: Remotes::MAX_NODES
|
89
107
|
o.bool '--help', 'Print instructions'
|
90
108
|
end
|
91
109
|
mine = Args.new(opts, @log).take || return
|
@@ -109,6 +127,8 @@ Available options:"
|
|
109
127
|
when 'update'
|
110
128
|
update(opts)
|
111
129
|
update(opts, false)
|
130
|
+
when 'select'
|
131
|
+
select(opts)
|
112
132
|
else
|
113
133
|
raise "Unknown command '#{command}'"
|
114
134
|
end
|
@@ -184,7 +204,7 @@ Available options:"
|
|
184
204
|
|
185
205
|
def trim(opts)
|
186
206
|
@remotes.all.each do |r|
|
187
|
-
remove(r[:host], r[:port], opts) if r[:errors] >
|
207
|
+
remove(r[:host], r[:port], opts) if r[:errors] > opts['tolerate']
|
188
208
|
end
|
189
209
|
@log.info("The list of remotes trimmed, #{@remotes.all.count} nodes left there")
|
190
210
|
end
|
@@ -231,6 +251,12 @@ in #{(Time.now - start).round(2)}s")
|
|
231
251
|
end
|
232
252
|
end
|
233
253
|
|
254
|
+
# @todo #292:30min Implement the logic of selecting the nodes as per #292.
|
255
|
+
# The strongest n nodes should be selected, where n = opts['max-nodes'].
|
256
|
+
def select(_opts)
|
257
|
+
raise NotImplementedError, 'This feature is not yet implemented.'
|
258
|
+
end
|
259
|
+
|
234
260
|
def terminate
|
235
261
|
@log.info("All threads before exit: #{Thread.list.map { |t| "#{t.name}/#{t.status}" }.join(', ')}")
|
236
262
|
require_relative '../node/front'
|
data/lib/zold/commands/taxes.rb
CHANGED
@@ -30,12 +30,24 @@ require_relative '../id'
|
|
30
30
|
require_relative '../tax'
|
31
31
|
require_relative '../http'
|
32
32
|
|
33
|
-
#
|
33
|
+
# Zold module.
|
34
34
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
35
35
|
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
36
36
|
# License:: MIT
|
37
37
|
module Zold
|
38
|
-
# Taxes command
|
38
|
+
# Taxes command.
|
39
|
+
#
|
40
|
+
# The user pays taxes for his/her wallet by running 'zold taxes pay'. As
|
41
|
+
# the White Paper explains (find it at http://papers.zold.io), each wallet
|
42
|
+
# has to pay certain amount of taxes in order to be accepted by any node
|
43
|
+
# in the network. Of course, a node may make a decision to accept and
|
44
|
+
# store any wallet, even if taxes are not paid, but the majority of
|
45
|
+
# nodes will obey the rules and will reject wallets that haven't paid
|
46
|
+
# enough taxes.
|
47
|
+
#
|
48
|
+
# Taxes are paid from wallet to wallet, not from clients to nodes. A wallet
|
49
|
+
# just selects the most suitable wallet to transfer taxes to and sends
|
50
|
+
# the payment. More details you can find in the White Paper.
|
39
51
|
class Taxes
|
40
52
|
def initialize(wallets:, remotes:, log: Log::Quiet.new)
|
41
53
|
@wallets = wallets
|
data/lib/zold/copies.rb
CHANGED
@@ -22,6 +22,7 @@ require 'time'
|
|
22
22
|
require 'csv'
|
23
23
|
require_relative 'atomic_file'
|
24
24
|
require_relative 'log'
|
25
|
+
require_relative 'wallet'
|
25
26
|
|
26
27
|
# The list of copies.
|
27
28
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
@@ -52,8 +53,8 @@ module Zold
|
|
52
53
|
list.reject! { |s| s[:time] < Time.now - 24 * 60 * 60 }
|
53
54
|
save(list)
|
54
55
|
deleted = 0
|
55
|
-
Dir.new(@dir).select { |f| f =~ /^[0-9]+$/ }.each do |f|
|
56
|
-
next unless list.find { |s| s[:name] == f }.nil?
|
56
|
+
Dir.new(@dir).select { |f| File.basename(f, Wallet::EXTENSION) =~ /^[0-9]+$/ }.each do |f|
|
57
|
+
next unless list.find { |s| s[:name] == File.basename(f, Wallet::EXTENSION) }.nil?
|
57
58
|
file = File.join(@dir, f)
|
58
59
|
size = File.size(file)
|
59
60
|
File.delete(file)
|
@@ -83,17 +84,17 @@ module Zold
|
|
83
84
|
FileUtils.mkdir_p(@dir)
|
84
85
|
list = load
|
85
86
|
target = list.find do |s|
|
86
|
-
f = File.join(@dir, s[:name])
|
87
|
+
f = File.join(@dir, "#{s[:name]}#{Wallet::EXTENSION}")
|
87
88
|
File.exist?(f) && AtomicFile.new(f).read == content
|
88
89
|
end
|
89
90
|
if target.nil?
|
90
91
|
max = Dir.new(@dir)
|
91
|
-
.select { |f| f =~ /^[0-9]+$/ }
|
92
|
+
.select { |f| File.basename(f, Wallet::EXTENSION) =~ /^[0-9]+$/ }
|
92
93
|
.map(&:to_i)
|
93
94
|
.max
|
94
95
|
max = 0 if max.nil?
|
95
96
|
name = (max + 1).to_s
|
96
|
-
AtomicFile.new(File.join(@dir, name)).write(content)
|
97
|
+
AtomicFile.new(File.join(@dir, "#{name}#{Wallet::EXTENSION}")).write(content)
|
97
98
|
else
|
98
99
|
name = target[:name]
|
99
100
|
end
|
@@ -115,12 +116,12 @@ module Zold
|
|
115
116
|
load.group_by { |s| s[:name] }.map do |name, scores|
|
116
117
|
{
|
117
118
|
name: name,
|
118
|
-
path: File.join(@dir, name),
|
119
|
+
path: File.join(@dir, "#{name}#{Wallet::EXTENSION}"),
|
119
120
|
score: scores.select { |s| s[:time] > Time.now - 24 * 60 * 60 }
|
120
121
|
.map { |s| s[:score] }
|
121
122
|
.inject(&:+) || 0
|
122
123
|
}
|
123
|
-
end.select { |c| File.exist?(c[:path]) }
|
124
|
+
end.select { |c| File.exist?(c[:path]) }.sort_by { |c| c[:score] }.reverse
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
@@ -153,7 +154,7 @@ module Zold
|
|
153
154
|
end
|
154
155
|
|
155
156
|
def file
|
156
|
-
File.join(@dir,
|
157
|
+
File.join(@dir, "scores#{Wallet::EXTENSION}")
|
157
158
|
end
|
158
159
|
end
|
159
160
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Zold
|
4
|
+
# Wallets decorator that adds missing wallets to the queue to be pulled later.
|
5
|
+
class HungryWallets < SimpleDelegator
|
6
|
+
# @todo #280:30min Add to the queue. Once in there, try
|
7
|
+
# to pull it as soon as possible as is described in #280.
|
8
|
+
def find(id)
|
9
|
+
super(id)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/zold/log.rb
CHANGED
@@ -22,12 +22,27 @@ require 'rainbow'
|
|
22
22
|
|
23
23
|
STDOUT.sync = true
|
24
24
|
|
25
|
-
#
|
25
|
+
# Zold module.
|
26
26
|
# Author:: Yegor Bugayenko (yegor256@gmail.com)
|
27
27
|
# Copyright:: Copyright (c) 2018 Yegor Bugayenko
|
28
28
|
# License:: MIT
|
29
29
|
module Zold
|
30
|
-
# Logging
|
30
|
+
# Logging facilities.
|
31
|
+
#
|
32
|
+
# There are a few logging classes, which can be used depending on what
|
33
|
+
# you want a user to see. There are three logging levels: INFO, ERROR,
|
34
|
+
# and DEBUG. In "quiet" mode the user won't see anything. This logging
|
35
|
+
# mode is used only for testing, when we don't want to see absolutely
|
36
|
+
# anything in the console. In order to turn off logging entirely, see
|
37
|
+
# how we configure it in test__helper.rb
|
38
|
+
#
|
39
|
+
# The default "regular" logging mode is what a user gets when he/she runs
|
40
|
+
# the gem in commmand line without any specific flags. In that case,
|
41
|
+
# the user will see only INFO and ERROR messages.
|
42
|
+
#
|
43
|
+
# In a "verbose" mode the user will see everything, including DEBUG
|
44
|
+
# messages. The user turns this mode by using --verbose command line argument.
|
45
|
+
#
|
31
46
|
module Log
|
32
47
|
# Extra verbose log
|
33
48
|
class Verbose
|
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
require 'concurrent'
|
22
22
|
require_relative '../log'
|
23
|
+
require_relative '../id'
|
23
24
|
require_relative '../verbose_thread'
|
24
25
|
|
25
26
|
# The async entrance of the web front.
|
@@ -29,20 +30,36 @@ require_relative '../verbose_thread'
|
|
29
30
|
module Zold
|
30
31
|
# The entrance
|
31
32
|
class AsyncEntrance
|
32
|
-
|
33
|
+
THREADS = Concurrent.processor_count * 4
|
34
|
+
|
35
|
+
def initialize(entrance, dir, log: Log::Quiet.new)
|
33
36
|
raise 'Entrance can\'t be nil' if entrance.nil?
|
34
37
|
@entrance = entrance
|
38
|
+
raise 'Directory can\'t be nil' if dir.nil?
|
39
|
+
raise 'Directory must be of type String' unless dir.is_a?(String)
|
40
|
+
@dir = dir
|
35
41
|
raise 'Log can\'t be nil' if log.nil?
|
36
42
|
@log = log
|
43
|
+
@mutex = Mutex.new
|
37
44
|
end
|
38
45
|
|
39
46
|
def start
|
40
47
|
@entrance.start do
|
48
|
+
FileUtils.mkdir_p(@dir)
|
41
49
|
@pool = Concurrent::FixedThreadPool.new(
|
42
|
-
|
43
|
-
max_queue: Concurrent.processor_count * 32,
|
44
|
-
fallback_policy: :abort
|
50
|
+
AsyncEntrance::THREADS, max_queue: AsyncEntrance::THREADS, fallback_policy: :abort
|
45
51
|
)
|
52
|
+
AsyncEntrance::THREADS.times do
|
53
|
+
@pool.post do
|
54
|
+
VerboseThread.new(@log).run(true) do
|
55
|
+
loop do
|
56
|
+
take
|
57
|
+
break if @pool.shuttingdown?
|
58
|
+
sleep Random.rand(100) / 100
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
46
63
|
begin
|
47
64
|
yield(self)
|
48
65
|
ensure
|
@@ -69,13 +86,35 @@ module Zold
|
|
69
86
|
end
|
70
87
|
|
71
88
|
def push(id, body)
|
72
|
-
@
|
73
|
-
|
74
|
-
|
89
|
+
@mutex.synchronize do
|
90
|
+
AtomicFile.new(File.join(@dir, id.to_s)).write(body)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def take
|
97
|
+
id = ''
|
98
|
+
body = ''
|
99
|
+
@mutex.synchronize do
|
100
|
+
opts = queue
|
101
|
+
unless opts.empty?
|
102
|
+
file = File.join(@dir, opts[0])
|
103
|
+
id = opts[0]
|
104
|
+
body = File.read(file)
|
105
|
+
File.delete(file)
|
75
106
|
end
|
76
107
|
end
|
77
|
-
|
78
|
-
|
108
|
+
return if id.empty? || body.empty?
|
109
|
+
start = Time.now
|
110
|
+
@entrance.push(Id.new(id), body)
|
111
|
+
@log.debug("Pushed #{id}/#{body.length}b to #{@entrance.class.name} in #{(Time.now - start).round}s")
|
112
|
+
end
|
113
|
+
|
114
|
+
def queue
|
115
|
+
Dir.new(@dir)
|
116
|
+
.select { |f| f =~ /^[0-9a-f]{16}$/ }
|
117
|
+
.sort_by { |f| File.mtime(File.join(@dir, f)) }
|
79
118
|
end
|
80
119
|
end
|
81
120
|
end
|
data/lib/zold/node/entrance.rb
CHANGED
@@ -37,7 +37,7 @@ module Zold
|
|
37
37
|
class Entrance
|
38
38
|
def initialize(wallets, remotes, copies, address, log: Log::Quiet.new)
|
39
39
|
raise 'Wallets can\'t be nil' if wallets.nil?
|
40
|
-
raise 'Wallets must
|
40
|
+
raise 'Wallets must implement the contract of Wallets: method #find is required' unless wallets.respond_to?(:find)
|
41
41
|
@wallets = wallets
|
42
42
|
raise 'Remotes can\'t be nil' if remotes.nil?
|
43
43
|
raise "Remotes must be of type Remotes: #{remotes.class.name}" unless remotes.is_a?(Remotes)
|
data/lib/zold/node/front.rb
CHANGED
@@ -49,6 +49,7 @@ module Zold
|
|
49
49
|
set :show_exceptions, false
|
50
50
|
set :server, 'webrick'
|
51
51
|
set :version, VERSION # to be injected at node.rb
|
52
|
+
set :protocol, PROTOCOL # to be injected at node.rb
|
52
53
|
set :ignore_score_weakness, false # to be injected at node.rb
|
53
54
|
set :reboot, false # to be injected at node.rb
|
54
55
|
set :home, nil? # to be injected at node.rb
|
@@ -89,10 +90,13 @@ module Zold
|
|
89
90
|
end
|
90
91
|
end
|
91
92
|
|
93
|
+
# @todo #357:30min Test that the headers are being set correctly.
|
94
|
+
# Currently there are no tests at all that would verify the headers.
|
92
95
|
after do
|
93
96
|
headers['Cache-Control'] = 'no-cache'
|
94
97
|
headers['Connection'] = 'close'
|
95
98
|
headers['X-Zold-Version'] = settings.version
|
99
|
+
headers['X-Zold-Protocol'] = settings.protocol.to_s
|
96
100
|
headers['Access-Control-Allow-Origin'] = '*'
|
97
101
|
headers[Http::SCORE_HEADER] = score.reduced(16).to_s
|
98
102
|
end
|
@@ -150,6 +154,8 @@ module Zold
|
|
150
154
|
{
|
151
155
|
version: settings.version,
|
152
156
|
score: score.to_h,
|
157
|
+
wallets: settings.wallets.all.count,
|
158
|
+
mtime: wallet.mtime.utc.iso8601,
|
153
159
|
body: AtomicFile.new(wallet.path).read
|
154
160
|
}.to_json
|
155
161
|
end
|
@@ -170,6 +176,22 @@ module Zold
|
|
170
176
|
wallet.key.to_s
|
171
177
|
end
|
172
178
|
|
179
|
+
get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/mtime} do
|
180
|
+
id = Id.new(params[:id])
|
181
|
+
wallet = settings.wallets.find(id)
|
182
|
+
error 404 unless wallet.exists?
|
183
|
+
content_type 'text/plain'
|
184
|
+
wallet.mtime.utc.iso8601.to_s
|
185
|
+
end
|
186
|
+
|
187
|
+
get %r{/wallet/(?<id>[A-Fa-f0-9]{16})/digest} do
|
188
|
+
id = Id.new(params[:id])
|
189
|
+
wallet = settings.wallets.find(id)
|
190
|
+
error 404 unless wallet.exists?
|
191
|
+
content_type 'text/plain'
|
192
|
+
wallet.digest
|
193
|
+
end
|
194
|
+
|
173
195
|
get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.txt} do
|
174
196
|
id = Id.new(params[:id])
|
175
197
|
wallet = settings.wallets.find(id)
|
@@ -177,7 +199,7 @@ module Zold
|
|
177
199
|
content_type 'text/plain'
|
178
200
|
[
|
179
201
|
wallet.network,
|
180
|
-
wallet.
|
202
|
+
wallet.protocol,
|
181
203
|
wallet.id.to_s,
|
182
204
|
wallet.key.to_s,
|
183
205
|
'',
|
@@ -185,7 +207,9 @@ module Zold
|
|
185
207
|
'',
|
186
208
|
'--',
|
187
209
|
"Balance: #{wallet.balance.to_zld}",
|
188
|
-
"Transactions: #{wallet.txns.count}"
|
210
|
+
"Transactions: #{wallet.txns.count}",
|
211
|
+
"Modified: #{wallet.mtime.utc.iso8601}",
|
212
|
+
"Digest: #{wallet.digest}"
|
189
213
|
].join("\n")
|
190
214
|
end
|
191
215
|
|
@@ -209,7 +233,8 @@ module Zold
|
|
209
233
|
settings.entrance.push(id, after)
|
210
234
|
JSON.pretty_generate(
|
211
235
|
version: settings.version,
|
212
|
-
score: score.to_h
|
236
|
+
score: score.to_h,
|
237
|
+
wallets: settings.wallets.all.count
|
213
238
|
)
|
214
239
|
end
|
215
240
|
|
@@ -56,9 +56,9 @@ module Zold
|
|
56
56
|
raise 'Id can\'t be nil' if id.nil?
|
57
57
|
raise 'Id must be of type Id' unless id.is_a?(Id)
|
58
58
|
raise 'Body can\'t be nil' if body.nil?
|
59
|
-
Tempfile.open do |f|
|
60
|
-
File.write(f
|
61
|
-
wallet = Wallet.new(f)
|
59
|
+
Tempfile.open(['', Wallet::EXTENSION]) do |f|
|
60
|
+
File.write(f, body)
|
61
|
+
wallet = Wallet.new(f.path)
|
62
62
|
unless wallet.network == @network
|
63
63
|
raise "The network name mismatch, the wallet is in '#{wallet.network}', we are in '#{@network}'"
|
64
64
|
end
|
@@ -41,7 +41,7 @@ module Zold
|
|
41
41
|
raise 'Entrance can\'t be nil' if entrance.nil?
|
42
42
|
@entrance = entrance
|
43
43
|
raise 'Wallets can\'t be nil' if wallets.nil?
|
44
|
-
raise 'Wallets must
|
44
|
+
raise 'Wallets must implement the contract of Wallets: method #find is required' unless wallets.respond_to?(:find)
|
45
45
|
@wallets = wallets
|
46
46
|
raise 'Remotes can\'t be nil' if remotes.nil?
|
47
47
|
raise 'Remotes must be of type Remotes' unless remotes.is_a?(Remotes)
|
data/lib/zold/patch.rb
CHANGED
@@ -33,14 +33,14 @@ module Zold
|
|
33
33
|
class Patch
|
34
34
|
def initialize(wallets, log: Log::Quiet.new)
|
35
35
|
raise 'Wallets can\'t be nil' if wallets.nil?
|
36
|
-
raise 'Wallets must
|
36
|
+
raise 'Wallets must implement the contract of Wallets: method #find is required' unless wallets.respond_to?(:find)
|
37
37
|
@wallets = wallets
|
38
38
|
@txns = []
|
39
39
|
@log = log
|
40
40
|
end
|
41
41
|
|
42
42
|
def to_s
|
43
|
-
return '
|
43
|
+
return 'nothing' if @txns.empty?
|
44
44
|
"#{@txns.count} txns"
|
45
45
|
end
|
46
46
|
|
@@ -50,7 +50,7 @@ module Zold
|
|
50
50
|
@key = wallet.key
|
51
51
|
if baseline
|
52
52
|
@txns = wallet.txns
|
53
|
-
@log.debug("The baseline
|
53
|
+
@log.debug("The baseline of #{wallet.id} is #{wallet.balance}/#{@txns.count}t")
|
54
54
|
else
|
55
55
|
@log.debug("The baseline of #{@txns.count} transactions ignored")
|
56
56
|
end
|
@@ -84,9 +84,13 @@ module Zold
|
|
84
84
|
@log.error("RSA signature is redundant at ##{txn.id} of #{wallet.id}: #{txn.to_text}")
|
85
85
|
next
|
86
86
|
end
|
87
|
+
unless wallet.key.to_s.include?(txn.prefix)
|
88
|
+
@log.error("Payment prefix doesn't match with the key of #{wallet.id}: #{txn.to_text}")
|
89
|
+
next
|
90
|
+
end
|
87
91
|
payer = @wallets.find(txn.bnf)
|
88
92
|
unless payer.exists?
|
89
|
-
@log.error("Paying wallet
|
93
|
+
@log.error("Paying wallet file is absent: #{txn.to_text}")
|
90
94
|
next
|
91
95
|
end
|
92
96
|
unless payer.has?(txn.id, wallet.id)
|
data/lib/zold/remotes.rb
CHANGED
@@ -39,6 +39,12 @@ module Zold
|
|
39
39
|
# At what amount of errors we delete the remote automatically
|
40
40
|
TOLERANCE = 8
|
41
41
|
|
42
|
+
# After this limit, the remote runtime must be recorded
|
43
|
+
RUNTIME_LIMIT = 16
|
44
|
+
|
45
|
+
# Default number of nodes to fetch.
|
46
|
+
MAX_NODES = 16
|
47
|
+
|
42
48
|
# Empty, for standalone mode
|
43
49
|
class Empty < Remotes
|
44
50
|
def initialize
|
@@ -180,6 +186,7 @@ module Zold
|
|
180
186
|
begin
|
181
187
|
yield Remotes::Remote.new(r[:host], r[:port], score, idx, log: log, network: @network)
|
182
188
|
idx += 1
|
189
|
+
raise 'Took too long to execute' if (Time.now - start).round > Remotes::RUNTIME_LIMIT
|
183
190
|
rescue StandardError => e
|
184
191
|
error(r[:host], r[:port])
|
185
192
|
errors = errors(r[:host], r[:port])
|
data/lib/zold/score.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 '
|
21
|
+
require 'openssl'
|
22
22
|
require 'time'
|
23
23
|
require_relative 'remotes'
|
24
24
|
|
@@ -101,7 +101,7 @@ module Zold
|
|
101
101
|
def hash
|
102
102
|
raise 'Score has zero value, there is no hash' if @suffixes.empty?
|
103
103
|
@suffixes.reduce(prefix) do |pfx, suffix|
|
104
|
-
Digest::SHA256.
|
104
|
+
OpenSSL::Digest::SHA256.new("#{pfx} #{suffix}").hexdigest
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
data/lib/zold/tax.rb
CHANGED
@@ -0,0 +1,57 @@
|
|
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 'log'
|
22
|
+
|
23
|
+
module Zold
|
24
|
+
# Class to manage data upgrades (when zold itself upgrades).
|
25
|
+
class Upgrades
|
26
|
+
def initialize(version, directory, log: Log::Verbose.new)
|
27
|
+
@version = version
|
28
|
+
@directory = directory
|
29
|
+
@log = log
|
30
|
+
end
|
31
|
+
|
32
|
+
# @todo #285:30min Write the upgrade manager tests that ensure:
|
33
|
+
# - Nothing breaks without the version file.
|
34
|
+
# - The upgrade scripts run when there is a version file and there are pending upgrade scripts.
|
35
|
+
# - Make sure *only* the correct upgrade scripts run.
|
36
|
+
def run
|
37
|
+
# This is a workaround, remove it once this class works correctly
|
38
|
+
require_relative '../../upgrades/2.rb'
|
39
|
+
UpgradeTo2.new(Dir.pwd, @log).exec
|
40
|
+
|
41
|
+
scripts.each do |script|
|
42
|
+
@version.apply(script)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def scripts
|
49
|
+
Dir.glob("#{@directory}/*.rb").sort.map do |path|
|
50
|
+
basename = File.basename(path)
|
51
|
+
match = basename.match(/^(\d+\.\d+\.\d+)\.rb$/)
|
52
|
+
raise 'An upgrade script has to be named <version>.rb.' unless match
|
53
|
+
match[1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/zold/version.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
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 'semantic'
|
22
|
+
require_relative 'log'
|
23
|
+
|
24
|
+
module Zold
|
25
|
+
# Read and write .zoldata/version.
|
26
|
+
class VersionFile
|
27
|
+
def initialize(path, log: Log::Verbose.new)
|
28
|
+
@path = path
|
29
|
+
@log = log
|
30
|
+
end
|
31
|
+
|
32
|
+
# @todo #285:30min Replace this stub with functionality.
|
33
|
+
# We need to run the script (`yield`) if the version of
|
34
|
+
# the script is between the saved version and the current one.
|
35
|
+
def apply(version)
|
36
|
+
@log.info("Version #{version} doesn't need to be applied.")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/zold/wallet.rb
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
21
|
require 'time'
|
22
|
+
require 'openssl'
|
22
23
|
require_relative 'version'
|
23
24
|
require_relative 'key'
|
24
25
|
require_relative 'id'
|
@@ -44,8 +45,12 @@ module Zold
|
|
44
45
|
# must have different names.
|
45
46
|
MAIN_NETWORK = 'zold'.freeze
|
46
47
|
|
48
|
+
# The extension of the wallet files
|
49
|
+
EXTENSION = '.z'.freeze
|
50
|
+
|
47
51
|
def initialize(file)
|
48
52
|
@file = file
|
53
|
+
@file = "#{file}#{EXTENSION}" if File.extname(file).empty?
|
49
54
|
end
|
50
55
|
|
51
56
|
def ==(other)
|
@@ -62,10 +67,10 @@ module Zold
|
|
62
67
|
n
|
63
68
|
end
|
64
69
|
|
65
|
-
def
|
70
|
+
def protocol
|
66
71
|
v = lines[1].strip
|
67
|
-
raise "Invalid version name '#{v}'" unless v =~ /^[0-9]+$/
|
68
|
-
v
|
72
|
+
raise "Invalid protocol version name '#{v}'" unless v =~ /^[0-9]+$/
|
73
|
+
v.to_i
|
69
74
|
end
|
70
75
|
|
71
76
|
def exists?
|
@@ -79,7 +84,7 @@ module Zold
|
|
79
84
|
def init(id, pubkey, overwrite: false, network: 'test')
|
80
85
|
raise "File '#{@file}' already exists" if File.exist?(@file) && !overwrite
|
81
86
|
raise "Invalid network name '#{network}'" unless network =~ /^[a-z]{4,16}$/
|
82
|
-
AtomicFile.new(@file).write("#{network}\
|
87
|
+
AtomicFile.new(@file).write("#{network}\n#{PROTOCOL}\n#{id}\n#{pubkey.to_pub}\n\n")
|
83
88
|
end
|
84
89
|
|
85
90
|
def root?
|
@@ -139,6 +144,14 @@ module Zold
|
|
139
144
|
end
|
140
145
|
end
|
141
146
|
|
147
|
+
def mtime
|
148
|
+
File.mtime(@file)
|
149
|
+
end
|
150
|
+
|
151
|
+
def digest
|
152
|
+
OpenSSL::Digest::SHA256.new(File.read(@file)).hexdigest
|
153
|
+
end
|
154
|
+
|
142
155
|
# Age of wallet in hours
|
143
156
|
def age
|
144
157
|
list = txns
|