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.
- checksums.yaml +4 -4
- data/README.md +3 -1
- data/bin/zold +21 -81
- data/fixtures/keys/1.pub +1 -0
- data/fixtures/keys/2 +51 -0
- data/fixtures/keys/2.pub +1 -0
- data/fixtures/scripts/print-helps.sh +15 -0
- data/fixtures/scripts/push-and-pull.sh +7 -3
- data/lib/zold/commands/clean.rb +24 -7
- data/lib/zold/commands/create.rb +16 -4
- data/lib/zold/commands/diff.rb +32 -9
- data/lib/zold/commands/fetch.rb +36 -9
- data/lib/zold/commands/invoice.rb +64 -0
- data/lib/zold/commands/list.rb +1 -1
- data/lib/zold/commands/merge.rb +31 -10
- data/lib/zold/commands/node.rb +6 -2
- data/lib/zold/commands/pay.rb +40 -17
- data/lib/zold/commands/propagate.rb +34 -14
- data/lib/zold/commands/push.rb +27 -9
- data/lib/zold/commands/remote.rb +63 -34
- data/lib/zold/commands/show.rb +34 -9
- data/lib/zold/copies.rb +4 -0
- data/lib/zold/http.rb +1 -1
- data/lib/zold/key.rb +17 -11
- data/lib/zold/node/farm.rb +8 -0
- data/lib/zold/node/front.rb +13 -3
- data/lib/zold/patch.rb +8 -8
- data/lib/zold/prefixes.rb +53 -0
- data/lib/zold/remotes.rb +6 -0
- data/lib/zold/score.rb +4 -4
- data/lib/zold/signature.rb +9 -9
- data/lib/zold/txn.rb +111 -0
- data/lib/zold/version.rb +1 -1
- data/lib/zold/wallet.rb +32 -59
- data/lib/zold/wallets.rb +2 -2
- data/test/commands/test_clean.rb +5 -4
- data/test/commands/test_create.rb +3 -3
- data/test/commands/test_diff.rb +16 -15
- data/test/commands/test_fetch.rb +13 -11
- data/test/commands/test_invoice.rb +46 -0
- data/test/commands/test_list.rb +4 -4
- data/test/commands/test_merge.rb +21 -22
- data/test/commands/test_node.rb +9 -9
- data/test/commands/test_pay.rb +12 -12
- data/test/commands/test_remote.rb +11 -11
- data/test/commands/test_show.rb +9 -7
- data/test/node/fake_node.rb +3 -3
- data/test/node/test_farm.rb +1 -1
- data/test/node/test_front.rb +6 -6
- data/test/test_amount.rb +1 -1
- data/test/test_copies.rb +1 -1
- data/test/test_http.rb +1 -1
- data/test/test_id.rb +1 -1
- data/test/test_key.rb +19 -1
- data/test/test_patch.rb +11 -11
- data/test/test_prefixes.rb +46 -0
- data/test/test_remotes.rb +1 -1
- data/test/test_score.rb +1 -1
- data/test/test_signature.rb +9 -11
- data/test/test_wallet.rb +22 -21
- data/test/test_wallets.rb +4 -4
- metadata +12 -1
data/lib/zold/commands/pay.rb
CHANGED
@@ -19,7 +19,7 @@
|
|
19
19
|
# SOFTWARE.
|
20
20
|
|
21
21
|
require 'slop'
|
22
|
-
require_relative '../log
|
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(
|
32
|
-
|
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.
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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 =
|
53
|
-
@log.debug("#{
|
54
|
-
@log.info(txn
|
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
|
22
|
-
require_relative '../wallet
|
23
|
-
require_relative '../wallets
|
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(
|
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
|
-
|
40
|
-
|
41
|
-
|
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
|
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
|
-
|
47
|
-
|
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(
|
70
|
+
@log.debug("Wallet #{me} propagated successfully")
|
51
71
|
end
|
52
72
|
end
|
53
73
|
end
|
data/lib/zold/commands/push.rb
CHANGED
@@ -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
|
23
|
-
require_relative '../
|
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(
|
33
|
-
@
|
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
|
-
|
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/#{
|
42
|
-
response = Http.new(uri).put(File.read(
|
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 #{
|
64
|
+
@log.info("The #{wallet.id} pushed to #{uri}")
|
47
65
|
end
|
48
66
|
end
|
49
67
|
end
|
data/lib/zold/commands/remote.rb
CHANGED
@@ -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
|
26
|
-
require_relative '../http
|
27
|
-
require_relative '../remotes
|
28
|
-
require_relative '../score
|
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
|
-
|
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
|
-
|
70
|
-
@log.debug('All remote nodes deleted')
|
70
|
+
clean
|
71
71
|
when 'reset'
|
72
|
-
|
73
|
-
@log.debug('Remote nodes set back to default')
|
72
|
+
reset
|
74
73
|
when 'add'
|
75
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/zold/commands/show.rb
CHANGED
@@ -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
|
-
|
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(
|
31
|
-
@
|
33
|
+
def initialize(wallets:, log: Log::Quiet.new)
|
34
|
+
@wallets = wallets
|
32
35
|
@log = log
|
33
36
|
end
|
34
37
|
|
35
|
-
def run(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
data/lib/zold/http.rb
CHANGED
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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
data/lib/zold/node/farm.rb
CHANGED
@@ -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
|
data/lib/zold/node/front.rb
CHANGED
@@ -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,
|
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
|
-
|
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(
|
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
|
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
|
39
|
-
max = negative.empty? ? 0 : negative.max_by
|
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
|
41
|
+
next if @txns.find { |t| t == txn }
|
42
42
|
next if
|
43
|
-
txn
|
44
|
-
(txn
|
45
|
-
@txns.find { |t| t
|
46
|
-
@txns.map
|
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
|