zold 0.13.32 → 0.13.33

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