zold 0.13.32 → 0.13.33

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: efe52b4e0be9a85e1a66d18a637d4495c6fd7409
4
- data.tar.gz: e698b73bda8784b6eb40d3ca9d50b175050938da
3
+ metadata.gz: a7fc6c26e23e3a3d056b786a847f4ea01b4f8763
4
+ data.tar.gz: f43869756f2b8eb4a7d63e22c6392ee297483246
5
5
  SHA512:
6
- metadata.gz: a1d4c2a547c39e6647cc44021edc81ade879816a09768f730823b143a287dec9b5fe69c6cdc34567d2afe8daa34975f0194d7511cde5ffb6b3f630d5071aa110
7
- data.tar.gz: e87c42f314772d1b789cedfc629e0551a41c7e980a8804c9403c9e37256870f8b73fc347845edd184af80e59379fcfd01a5378d94d36ca2e14b173d79add425f
6
+ metadata.gz: 896be48716e75a94ff60de18c026ca36eeba4a558d2fbad47a7aa934dab4543724d23df676e778a20f28c19a2aa93a8504a1a099832f04013626adc4f3568534
7
+ data.tar.gz: 34aed103cece3a518ea2d9d17a8f5c03e2ee7ba495341a0e8a50ae85c32115a3717811eba5118661d6e35424032736f7a0f26f37e7fdd2f25a80fe93a28332a3
data/heroku-run.sh CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
 
3
- ./bin/zold node --no-colors --trace --verbose \
3
+ ./bin/zold node --no-colors --trace \
4
4
  --bind-port=$PORT --port=80 --host=b1.zold.io --threads=0 \
5
5
  --invoice=ML5Ern7m@912ecc24b32dbe74 --never-reboot \
6
6
  --bonus-wallet=81c9c25789b03876 --private-key=bonus.key --bonus-amount=1 --bonus-time=60
@@ -65,7 +65,7 @@ Available options:"
65
65
  def diff(wallet, cps, _)
66
66
  raise "There are no remote copies, try 'zold fetch' first" if cps.all.empty?
67
67
  cps = cps.all.sort_by { |c| c[:score] }.reverse
68
- patch = Patch.new(log: @log)
68
+ patch = Patch.new(@wallets, log: @log)
69
69
  cps.each do |c|
70
70
  patch.join(Wallet.new(c[:path]))
71
71
  end
@@ -45,6 +45,9 @@ module Zold
45
45
  opts = Slop.parse(args, help: true, suppress_errors: true) do |o|
46
46
  o.banner = "Usage: zold merge [ID...] [options]
47
47
  Available options:"
48
+ o.bool '--no-baseline',
49
+ 'Don\'t trust any remote copies and re-validate all incoming payments against their wallets',
50
+ default: false
48
51
  o.bool '--help', 'Print instructions'
49
52
  end
50
53
  mine = Args.new(opts, @log).take || return
@@ -61,20 +64,20 @@ Available options:"
61
64
 
62
65
  private
63
66
 
64
- def merge(id, cps, _)
67
+ def merge(id, cps, opts)
65
68
  if cps.all.empty?
66
69
  @log.error("There are no remote copies of #{id}, try 'zold fetch' first")
67
70
  return
68
71
  end
69
72
  cps = cps.all.sort_by { |c| c[:score] }.reverse
70
- patch = Patch.new(log: @log)
73
+ patch = Patch.new(@wallets, log: @log)
71
74
  cps.each do |c|
72
- merge_one(patch, Wallet.new(c[:path]), "#{c[:host]}:#{c[:port]}")
75
+ merge_one(opts, patch, Wallet.new(c[:path]), "#{c[:host]}:#{c[:port]}")
73
76
  @log.debug("#{c[:host]}:#{c[:port]} merged: #{patch}")
74
77
  end
75
78
  wallet = @wallets.find(id)
76
79
  if wallet.exists?
77
- merge_one(patch, wallet, 'localhost')
80
+ merge_one(opts, patch, wallet, 'localhost')
78
81
  @log.debug("Local copy merged: #{patch}")
79
82
  else
80
83
  @log.debug("Local copy is absent, won't merge")
@@ -88,8 +91,8 @@ Available options:"
88
91
  modified
89
92
  end
90
93
 
91
- def merge_one(patch, wallet, name)
92
- patch.join(wallet)
94
+ def merge_one(opts, patch, wallet, name)
95
+ patch.join(wallet, !opts['no-baseline'])
93
96
  rescue StandardError => e
94
97
  @log.error("Can't merge a copy coming from #{name}: #{e.message}")
95
98
  @log.debug(Backtrace.new(e).to_s)
@@ -158,6 +158,7 @@ module Zold
158
158
  farm.start(host, opts[:port], threads: opts[:threads], strength: opts[:strength]) do
159
159
  Front.set(:farm, farm)
160
160
  metronome = metronome(farm, entrance, opts)
161
+ Front.set(:metronome, metronome)
161
162
  begin
162
163
  @log.info("Starting up the web front at http://#{host}:#{opts[:port]}...")
163
164
  Front.run!
@@ -33,6 +33,12 @@ module Zold
33
33
  @threads = []
34
34
  end
35
35
 
36
+ def to_text
37
+ @threads.map do |t|
38
+ "#{t.name}: status=#{t.status}; alive=#{t.alive?};\n #{t.backtrace.join("\n ")}"
39
+ end.join("\n")
40
+ end
41
+
36
42
  def add(routine)
37
43
  @threads << Thread.start do
38
44
  Thread.current.name = routine.class.name
@@ -131,7 +131,7 @@ and modified nothing (this is most likely a bug!)")
131
131
  ).run(['fetch', id.to_s, "--ignore-node=#{@address}"])
132
132
  modified = Merge.new(
133
133
  wallets: @wallets, copies: copies.root, log: @log
134
- ).run(['merge', id.to_s])
134
+ ).run(['merge', id.to_s, '--no-baseline'])
135
135
  Clean.new(wallets: @wallets, copies: copies.root, log: @log).run(['clean', id.to_s])
136
136
  copies.remove(localhost, Remotes::PORT)
137
137
  spread(modified)
@@ -56,6 +56,7 @@ module Zold
56
56
  set :log, nil? # to be injected at node.rb
57
57
  set :address, nil? # to be injected at node.rb
58
58
  set :farm, nil? # to be injected at node.rb
59
+ set :metronome, nil? # to be injected at node.rb
59
60
  set :entrance, nil? # to be injected at node.rb
60
61
  set :wallets, nil? # to be injected at node.rb
61
62
  set :remotes, nil? # to be injected at node.rb
@@ -201,6 +202,11 @@ module Zold
201
202
  settings.farm.to_text
202
203
  end
203
204
 
205
+ get '/metronome' do
206
+ content_type 'text/plain'
207
+ settings.metronome.to_text
208
+ end
209
+
204
210
  not_found do
205
211
  status 404
206
212
  content_type 'text/plain'
data/lib/zold/patch.rb CHANGED
@@ -31,21 +31,29 @@ require_relative 'atomic_file'
31
31
  module Zold
32
32
  # A patch
33
33
  class Patch
34
- def initialize(log: Log::Quiet.new)
34
+ def initialize(wallets, log: Log::Quiet.new)
35
+ raise 'Wallets can\'t be nil' if wallets.nil?
36
+ raise 'Wallets must be of type Wallets' unless wallets.is_a?(Wallets)
37
+ @wallets = wallets
38
+ @txns = []
35
39
  @log = log
36
40
  end
37
41
 
38
42
  def to_s
39
- return 'empty' if @id.nil?
43
+ return 'empty' if @txns.empty?
40
44
  "#{@txns.count} txns"
41
45
  end
42
46
 
43
- def join(wallet)
47
+ def join(wallet, baseline = true)
44
48
  if @id.nil?
45
49
  @id = wallet.id
46
50
  @key = wallet.key
47
- @txns = wallet.txns
48
- @log.debug("The baseline: #{@txns.count} transactions, the balance is #{wallet.balance}")
51
+ if baseline
52
+ @txns = wallet.txns
53
+ @log.debug("The baseline: #{@txns.count} transactions, the balance is #{wallet.balance}")
54
+ else
55
+ @log.debug("The baseline of #{@txns.count} transactions ignored")
56
+ end
49
57
  @network = wallet.network
50
58
  end
51
59
  if wallet.network != @network
@@ -53,17 +61,16 @@ module Zold
53
61
  end
54
62
  raise 'Public key mismatch' if wallet.key != @key
55
63
  raise "Wallet ID mismatch: #{@id} != #{wallet.id}" if wallet.id != @id
56
- negative = @txns.select { |t| t.amount.negative? }
57
- max = negative.empty? ? 0 : negative.max_by(&:id).id
64
+ max = @txns.select { |t| t.amount.negative? }.map(&:id).max.to_i
58
65
  wallet.txns.each do |txn|
59
66
  next if @txns.find { |t| t == txn }
60
67
  if txn.amount.negative?
61
68
  if txn.id <= max
62
- @log.debug("Transaction ID is less than max #{max}: #{txn.to_text}")
69
+ @log.error("Transaction ID is less than max #{max}: #{txn.to_text}")
63
70
  next
64
71
  end
65
72
  if @txns.find { |t| t.id == txn.id }
66
- @log.debug("Transaction ##{txn.id} already exists: #{txn.to_text}")
73
+ @log.error("Transaction ##{txn.id} already exists: #{txn.to_text}")
67
74
  next
68
75
  end
69
76
  if !@txns.empty? && @txns.map(&:amount).inject(&:+) < txn.amount
@@ -74,9 +81,20 @@ module Zold
74
81
  @log.error("Invalid RSA signature at transaction ##{txn.id} of #{wallet.id}: #{txn.to_text}")
75
82
  next
76
83
  end
77
- elsif !txn.sign.nil? && !txn.sign.empty?
78
- @log.error("RSA signature is redundant at ##{txn.id} of #{wallet.id}: #{txn.to_text}")
79
- next
84
+ else
85
+ if !txn.sign.nil? && !txn.sign.empty?
86
+ @log.error("RSA signature is redundant at ##{txn.id} of #{wallet.id}: #{txn.to_text}")
87
+ next
88
+ end
89
+ payer = @wallets.find(txn.bnf)
90
+ unless payer.exists?
91
+ @log.error("Paying wallet #{wallet.id} is absent at ##{txn.id}: #{txn.to_text}")
92
+ next
93
+ end
94
+ unless payer.has?(txn.id, wallet.id)
95
+ @log.error("Paying wallet #{wallet.id} doesn't have transaction ##{txn.id}: #{txn.to_text}")
96
+ next
97
+ end
80
98
  end
81
99
  @log.debug("Merged on top: #{txn.to_text}")
82
100
  @txns << txn
data/lib/zold/txn.rb CHANGED
@@ -32,6 +32,12 @@ require_relative 'signature'
32
32
  module Zold
33
33
  # A single transaction
34
34
  class Txn
35
+ # Regular expression for details
36
+ RE_DETAILS = '[a-zA-Z0-9 @\!\?\*_\-\.:,\']+'.freeze
37
+
38
+ # Regular expression for prefix
39
+ RE_PREFIX = '[a-zA-Z0-9]+'.freeze
40
+
35
41
  attr_reader :id, :date, :amount, :prefix, :bnf, :details, :sign
36
42
  attr_writer :sign, :amount, :bnf
37
43
  def initialize(id, date, amount, prefix, bnf, details)
@@ -52,12 +58,12 @@ module Zold
52
58
  raise 'Prefix can\'t be NIL' if prefix.nil?
53
59
  raise "Prefix is too short: \"#{prefix}\"" if prefix.length < 8
54
60
  raise "Prefix is too long: \"#{prefix}\"" if prefix.length > 32
55
- raise "Prefix is wrong: \"#{prefix}\"" unless prefix =~ /^[a-zA-Z0-9]+$/
61
+ raise "Prefix is wrong: \"#{prefix}\" (#{Txn::RE_PREFIX})" unless prefix =~ Regexp.new("^#{Txn::RE_PREFIX}$")
56
62
  @prefix = prefix
57
63
  raise 'Details can\'t be NIL' if details.nil?
58
64
  raise 'Details can\'t be empty' if details.empty?
59
65
  raise "Details are too long: \"#{details}\"" if details.length > 512
60
- raise "Details are wrong: \"#{details}\"" unless details =~ /^[a-zA-Z0-9 -\.,]+$/
66
+ raise "Wrong details \"#{details}\" (#{Txn::RE_DETAILS})" unless details =~ Regexp.new("^#{Txn::RE_DETAILS}$")
61
67
  @details = details
62
68
  end
63
69
 
@@ -103,14 +109,14 @@ module Zold
103
109
  '([0-9a-f]{4})',
104
110
  '([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z)',
105
111
  '([0-9a-f]{16})',
106
- '([A-Za-z0-9]{8,32})',
112
+ "(#{Txn::RE_PREFIX})",
107
113
  '([0-9a-f]{16})',
108
- '([a-zA-Z0-9 -\.,]{1,512})',
114
+ "(#{Txn::RE_DETAILS})",
109
115
  '([A-Za-z0-9+/]+={0,3})?'
110
116
  ].join(';') + '$'
111
117
  )
112
118
  clean = line.strip
113
- raise "Invalid line ##{idx}: #{line.inspect}" unless regex.match(clean)
119
+ raise "Invalid line ##{idx}: #{line.inspect} #{regex}" unless regex.match(clean)
114
120
  parts = clean.split(';')
115
121
  txn = Txn.new(
116
122
  Hexnum.parse(parts[0]).to_i,
data/lib/zold/version.rb CHANGED
@@ -23,5 +23,5 @@
23
23
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
24
24
  # License:: MIT
25
25
  module Zold
26
- VERSION = '0.13.32'.freeze
26
+ VERSION = '0.13.33'.freeze
27
27
  end
@@ -106,4 +106,19 @@ class TestMerge < Minitest::Test
106
106
  assert(!wallet.balance.zero?)
107
107
  end
108
108
  end
109
+
110
+ def test_rejects_fake_positives_in_new_wallet
111
+ FakeHome.new.run do |home|
112
+ main = home.create_wallet
113
+ remote = home.create_wallet
114
+ File.write(remote.path, File.read(main.path))
115
+ remote.add(Zold::Txn.new(1, Time.now, Zold::Amount.new(zld: 11.0), 'NOPREFIX', Zold::Id.new, 'fake'))
116
+ copies = home.copies(main)
117
+ copies.add(File.read(remote.path), 'fake-host', 80, 0)
118
+ Zold::Merge.new(wallets: home.wallets, copies: copies.root, log: test_log).run(
119
+ ['merge', main.id.to_s, '--no-baseline']
120
+ )
121
+ assert_equal(Zold::Amount::ZERO, main.balance)
122
+ end
123
+ end
109
124
  end
@@ -37,6 +37,7 @@ class FrontTest < Minitest::Test
37
37
  '/remotes',
38
38
  '/version',
39
39
  '/farm',
40
+ '/metronome',
40
41
  '/score'
41
42
  ],
42
43
  '404' => [
data/test/test_patch.rb CHANGED
@@ -46,13 +46,28 @@ class TestPatch < Minitest::Test
46
46
  File.write(third.path, File.read(first.path))
47
47
  t = third.sub(Zold::Amount.new(zld: 10.0), "NOPREFIX@#{Zold::Id.new}", key)
48
48
  third.add(t.inverse(first.id))
49
- patch = Zold::Patch.new(log: test_log)
49
+ patch = Zold::Patch.new(home.wallets, log: test_log)
50
50
  patch.join(first)
51
51
  patch.join(second)
52
52
  patch.join(third)
53
53
  FileUtils.rm(first.path)
54
54
  assert_equal(true, patch.save(first.path))
55
- assert_equal(Zold::Amount.new(zld: -43.0), first.balance)
55
+ assert_equal(Zold::Amount.new(zld: -53.0), first.balance)
56
+ end
57
+ end
58
+
59
+ def test_rejects_fake_positives
60
+ FakeHome.new.run do |home|
61
+ first = home.create_wallet
62
+ second = home.create_wallet
63
+ File.write(second.path, File.read(first.path))
64
+ second.add(Zold::Txn.new(1, Time.now, Zold::Amount.new(zld: 11.0), 'NOPREFIX', Zold::Id.new, 'fake'))
65
+ patch = Zold::Patch.new(home.wallets, log: test_log)
66
+ patch.join(first)
67
+ patch.join(second)
68
+ FileUtils.rm(first.path)
69
+ assert_equal(true, patch.save(first.path))
70
+ assert_equal(Zold::Amount::ZERO, first.balance)
56
71
  end
57
72
  end
58
73
  end
data/test/test_txn.rb CHANGED
@@ -43,4 +43,16 @@ class TestTxn < Minitest::Test
43
43
  assert_equal('-99.95', txn.amount.to_zld)
44
44
  assert_equal('NOPREFIX', txn.prefix)
45
45
  end
46
+
47
+ def test_accepts_text_as_details
48
+ details = 'How are you, dude?! I\'m @yegor256: *_hello_'
49
+ txn = Zold::Txn.parse(
50
+ Zold::Txn.new(
51
+ 123, Time.now, Zold::Amount.new(zld: -99.95),
52
+ 'NOPREFIX', Zold::Id.new,
53
+ details
54
+ ).to_s
55
+ )
56
+ assert_equal(details, txn.details)
57
+ end
46
58
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.32
4
+ version: 0.13.33
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-18 00:00:00.000000000 Z
11
+ date: 2018-06-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby