zold 0.26.16 → 0.26.17

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
  SHA256:
3
- metadata.gz: 7b0ae546cf6416b998bfd75490dae5b662d89c3e6e598958f1c73c86e51fda17
4
- data.tar.gz: bcc69294f383f7832b3ac832742c89630eb7d8a880741aefd7c0397a88cb9042
3
+ metadata.gz: a0649d6ccb04536eabe73074ef91e87317c15f35b64aea31739e8b82d4bb5347
4
+ data.tar.gz: 788a6dfd9cb3a6cc687f1abe29a9636d3d3245e7e745481fdab8ebe7c96de753
5
5
  SHA512:
6
- metadata.gz: f746fba3dad2802bce2170facba75809d44fee26f29cf61a95c1c74245c61668f1fd75fed47722ab50bfc7676093b287b16e4e1683b280f99fd18715321e8574
7
- data.tar.gz: fe15c859e3ff938901b7ef369e80346dc005be4451e4c2abd2cd45bd778b0b5b8939c3d0201ae78e4802c8d3aec6475779d94ab96a12857eec61e3e8d9476a7b
6
+ metadata.gz: dc2b1ad12503d024a5a31600186a7084b6def9d978fd4e531037f38d5d6e12160c2b47cd716adc89f02f474b4af52dd80e933666e07ed5b2f0d4062e28f5d0e4
7
+ data.tar.gz: f14755645f55d6d954c3bd38668d961ce6121399d6b2f1dc5b13b25ed596f9c4f1527fd75ecf06391401f713653a621a6114f135b216ae77b5d6352046cd2693
@@ -20,11 +20,11 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  # SOFTWARE.
22
22
 
23
- require_relative 'thread_badge'
24
23
  require 'tempfile'
25
24
  require 'slop'
26
25
  require 'diffy'
27
26
  require 'rainbow'
27
+ require_relative 'thread_badge'
28
28
  require_relative 'args'
29
29
  require_relative '../log'
30
30
  require_relative '../patch'
@@ -45,7 +45,8 @@ require_relative '../node/async_entrance'
45
45
  require_relative '../node/sync_entrance'
46
46
  require_relative '../node/nodup_entrance'
47
47
  require_relative '../node/nospam_entrance'
48
- require_relative '../node/journaled_entrance'
48
+ require_relative '../node/pipeline'
49
+ require_relative '../node/journaled_pipeline'
49
50
  require_relative '../node/front'
50
51
  require_relative '../node/trace'
51
52
  require_relative '../node/farm'
@@ -271,25 +272,23 @@ the node won\'t connect to the network like that; try to do "zold remote reset"
271
272
  FileUtils.mkdir_p(journal_dir)
272
273
  Front.set(:journal_dir, journal_dir)
273
274
  Front.set(:node_alias, node_alias(opts, address))
274
- jlog = Logger.new(File.join(journal_dir, 'journal'))
275
- jlog.level = Logger::DEBUG
276
- jlog.formatter = Log::COMPACT
277
275
  entrance = SafeEntrance.new(
278
276
  NoSpamEntrance.new(
279
277
  NoDupEntrance.new(
280
278
  AsyncEntrance.new(
281
279
  SpreadEntrance.new(
282
280
  SyncEntrance.new(
283
- JournaledEntrance.new(
284
- Entrance.new(
285
- wts, @remotes, @copies, address,
286
- ledger: ledger,
287
- log: Log::Tee.new(@log, jlog), network: opts['network']
288
- ),
281
+ Entrance.new(
289
282
  wts,
290
- journal_dir,
291
- jlog,
292
- 'journal'
283
+ JournaledPipeline.new(
284
+ Pipeline.new(
285
+ @remotes, @copies, address,
286
+ ledger: ledger,
287
+ network: opts['network']
288
+ ),
289
+ journal_dir
290
+ ),
291
+ log: @log
293
292
  ),
294
293
  File.join(home, '.zoldata/sync-entrance'),
295
294
  log: @log
@@ -58,7 +58,7 @@ module Zold
58
58
  if @queue.size > 256
59
59
  @log.error("Hungry queue is full with #{@queue.size} wallets, can't add #{id}")
60
60
  elsif @missed.exists?(id.to_s)
61
- @log.debug("Hungry queue has seen #{id} just #{Age.new(@missed.mtime(id.to_s))} ago
61
+ @log.debug("Hungry queue has seen #{id} just #{Age.new(@missed.mtime(id.to_s))} ago \
62
62
  (amoung #{@missed.size} others) and it was not found")
63
63
  else
64
64
  @mutex.synchronize do
@@ -21,15 +21,8 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'tempfile'
24
+ require 'time'
24
25
  require_relative '../log'
25
- require_relative '../remotes'
26
- require_relative '../copies'
27
- require_relative '../tax'
28
- require_relative '../age'
29
- require_relative '../commands/clean'
30
- require_relative '../commands/merge'
31
- require_relative '../commands/fetch'
32
- require_relative '../commands/push'
33
26
 
34
27
  # The entrance of the web front.
35
28
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
@@ -38,18 +31,13 @@ require_relative '../commands/push'
38
31
  module Zold
39
32
  # The entrance
40
33
  class Entrance
41
- def initialize(wallets, remotes, copies, address, ledger: '/dev/null',
42
- log: Log::NULL, network: 'test')
34
+ def initialize(wallets, pipeline, log: Log::NULL)
43
35
  @wallets = wallets
44
- @remotes = remotes
45
- @copies = copies
46
- @address = address
36
+ @pipeline = pipeline
47
37
  @log = log
48
- @network = network
49
38
  @history = []
50
39
  @speed = []
51
40
  @mutex = Mutex.new
52
- @ledger = ledger
53
41
  end
54
42
 
55
43
  def start
@@ -62,7 +50,7 @@ module Zold
62
50
  'history': @history.join(', '),
63
51
  'history_size': @history.count,
64
52
  'speed': @speed.empty? ? 0 : (@speed.inject(&:+) / @speed.count),
65
- 'ledger': File.exist?(@ledger) ? IO.read(@ledger).split("\n").count : 0
53
+ 'pipeline': @pipeline.to_json
66
54
  }
67
55
  end
68
56
 
@@ -72,25 +60,7 @@ module Zold
72
60
  raise 'Id must be of type Id' unless id.is_a?(Id)
73
61
  raise 'Body can\'t be nil' if body.nil?
74
62
  start = Time.now
75
- copies = Copies.new(File.join(@copies, id.to_s))
76
- host = '0.0.0.0'
77
- copies.add(body, host, Remotes::PORT, 0)
78
- unless @remotes.all.empty?
79
- Fetch.new(
80
- wallets: @wallets, remotes: @remotes, copies: copies.root, log: @log
81
- ).run(['fetch', id.to_s, "--ignore-node=#{@address}", "--network=#{@network}", '--quiet-if-absent'])
82
- end
83
- modified = merge(id, copies)
84
- Clean.new(wallets: @wallets, copies: copies.root, log: @log).run(
85
- ['clean', id.to_s, '--max-age=1']
86
- )
87
- copies.remove(host, Remotes::PORT)
88
- if modified.empty?
89
- @log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and not modified anything")
90
- else
91
- @log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and modified #{modified.join(', ')}")
92
- end
93
- modified << id if copies.all.count > 1
63
+ modified = @pipeline.push(id, body, @wallets, @log)
94
64
  sec = (Time.now - start).round(2)
95
65
  @mutex.synchronize do
96
66
  @history.shift if @history.length >= 16
@@ -102,27 +72,5 @@ module Zold
102
72
  end
103
73
  modified
104
74
  end
105
-
106
- def merge(id, copies)
107
- Tempfile.open do |f|
108
- modified = Merge.new(
109
- wallets: @wallets, remotes: @remotes, copies: copies.root, log: @log
110
- ).run(['merge', id.to_s, "--ledger=#{f.path}", "--network=#{@network}"])
111
- @mutex.synchronize do
112
- txns = File.exist?(@ledger) ? IO.read(@ledger).strip.split("\n") : []
113
- txns += IO.read(f.path).strip.split("\n")
114
- IO.write(
115
- @ledger,
116
- txns.map { |t| t.split(';') }
117
- .uniq { |t| "#{t[1]}-#{t[3]}" }
118
- .reject { |t| Txn.parse_time(t[0]) < Time.now - 24 * 60 * 60 }
119
- .map { |t| t.join(';') }
120
- .join("\n")
121
- .strip
122
- )
123
- end
124
- modified
125
- end
126
- end
127
75
  end
128
76
  end
@@ -319,7 +319,7 @@ this is not a normal behavior, you may want to report a bug to our GitHub reposi
319
319
  end
320
320
 
321
321
  get %r{/wallet/(?<id>[A-Fa-f0-9]{16})\.html} do
322
- fetch do |wallet|
322
+ fetch('text/html') do |wallet|
323
323
  [
324
324
  '<!DOCTYPE html><html><head>',
325
325
  '<title>' + wallet.id.to_s + '</title>',
@@ -338,8 +338,8 @@ this is not a normal behavior, you may want to report a bug to our GitHub reposi
338
338
  '<td style="color:' + (t.amount.negative? ? 'red' : 'green') + "\">#{t.id}</td>",
339
339
  "<td>#{t.date.utc.iso8601}</td>",
340
340
  '<td style="text-align:right">' + t.amount.to_zld(4) + '</td>',
341
- "<td><a href='/wallet/#{t.bnf}.html'>#{t.bnf}</td>",
342
- "<td>#{t.details}</td>",
341
+ "<td><a href='/wallet/#{t.bnf}.html'><code>#{t.bnf}</code></a></td>",
342
+ "<td>#{CGI.escapeHTML(t.details)}</td>",
343
343
  '</tr>'
344
344
  ].join
345
345
  end.join,
@@ -350,7 +350,7 @@ this is not a normal behavior, you may want to report a bug to our GitHub reposi
350
350
  "File size: #{Size.new(wallet.size)}/#{wallet.size}, \
351
351
  #{Copies.new(File.join(settings.copies, wallet.id)).all.count} copies<br/>",
352
352
  "Modified: #{wallet.mtime.utc.iso8601} (#{Age.new(wallet.mtime.utc.iso8601)} ago)<br/>",
353
- "Digest: #{wallet.digest}</p></section></body></html>"
353
+ "Digest: <code>#{wallet.digest}</code></p></section></body></html>"
354
354
  ].join
355
355
  end
356
356
  end
@@ -488,7 +488,8 @@ this is not a normal behavior, you may want to report a bug to our GitHub reposi
488
488
  '<link href="https://cdn.jsdelivr.net/gh/yegor256/tacit@gh-pages/tacit-css-1.4.2.min.css" rel="stylesheet"/>',
489
489
  '</head><body><section>',
490
490
  DirItems.new(settings.journal_dir).fetch.sort.map do |f|
491
- "<p><a href='/journal/item?id=#{f}'>#{f}</a></p>"
491
+ file = File.join(settings.journal_dir, f)
492
+ "<p><a href='/journal/item?id=#{f}'>#{f}</a>: #{File.size(file)} #{Age.new(File.mtime(file))} ago</p>"
492
493
  end.join,
493
494
  '</section></body></html>'
494
495
  ].join
@@ -21,23 +21,18 @@
21
21
  # SOFTWARE.
22
22
 
23
23
  require 'tempfile'
24
+ require 'diffy'
25
+ require_relative 'pipeline'
24
26
  require_relative '../log'
25
- require_relative '../remotes'
26
- require_relative '../copies'
27
- require_relative '../tax'
28
27
  require_relative '../age'
29
- require_relative '../commands/clean'
30
- require_relative '../commands/merge'
31
- require_relative '../commands/fetch'
32
- require_relative '../commands/push'
33
28
 
34
- # The entrance with journals.
29
+ # The pipeline with journals.
35
30
  # Author:: Yegor Bugayenko (yegor256@gmail.com)
36
31
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
37
32
  # License:: MIT
38
33
  module Zold
39
34
  # The entrance that keeps a journal for each wallet
40
- class JournaledEntrance
35
+ class JournaledPipeline
41
36
  # Decorated wallets
42
37
  class Wallets < SimpleDelegator
43
38
  def initialize(wallets, log)
@@ -61,12 +56,9 @@ module Zold
61
56
  end
62
57
  end
63
58
 
64
- def initialize(entrance, wallets, dir, log, journal)
65
- @wallets = JournaledEntrance::Wallets.new(wallets, log)
66
- @entrance = entrance
67
- @dir = File.expand_path(dir)
68
- @log = log
69
- @journal = File.join(@dir, journal)
59
+ def initialize(pipeline, dir)
60
+ @pipeline = pipeline
61
+ @dir = dir
70
62
  end
71
63
 
72
64
  def start
@@ -76,21 +68,24 @@ module Zold
76
68
  end
77
69
 
78
70
  def to_json
79
- @entrance.to_json.merge(
71
+ @pipeline.to_json.merge(
80
72
  'dir': @dir
81
73
  )
82
74
  end
83
75
 
84
76
  # Returns a list of modifed wallets (as Zold::Id)
85
- def push(id, body)
77
+ def push(id, body, wallets, log)
86
78
  DirItems.new(@dir).fetch.each do |f|
87
79
  f = File.join(@dir, f)
88
80
  File.delete(f) if File.mtime(f) < Time.now - 24 * 60 * 60
89
81
  end
90
- @log.info("push(#{id}, #{body.length} bytes)")
91
- modified = @entrance.push(id, body)
92
- IO.write(File.join(@dir, "#{Time.now.utc.iso8601.gsub(/[^0-9]/, '-')}-#{id}"), IO.read(@journal))
93
- modified
82
+ journal = File.join(@dir, "#{Time.now.utc.iso8601.gsub(/[^0-9]/, '-')}-#{id}")
83
+ jlog = Logger.new(journal)
84
+ jlog.level = Logger::DEBUG
85
+ jlog.formatter = Log::COMPACT
86
+ tlog = Log::Tee.new(log, jlog)
87
+ tlog.info("push(#{id}, #{body.length} bytes)")
88
+ @pipeline.push(id, body, JournaledPipeline::Wallets.new(wallets, jlog), tlog)
94
89
  end
95
90
  end
96
91
  end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2019 Zerocracy, Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the 'Software'), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in all
13
+ # copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ # SOFTWARE.
22
+
23
+ require 'tempfile'
24
+ require_relative '../log'
25
+ require_relative '../remotes'
26
+ require_relative '../copies'
27
+ require_relative '../tax'
28
+ require_relative '../age'
29
+ require_relative '../commands/clean'
30
+ require_relative '../commands/merge'
31
+ require_relative '../commands/fetch'
32
+ require_relative '../commands/push'
33
+
34
+ # The pipeline that accepts new wallets and merges them.
35
+ # Author:: Yegor Bugayenko (yegor256@gmail.com)
36
+ # Copyright:: Copyright (c) 2018 Yegor Bugayenko
37
+ # License:: MIT
38
+ module Zold
39
+ # The pipeline
40
+ class Pipeline
41
+ def initialize(remotes, copies, address, ledger: '/dev/null', network: 'test')
42
+ @remotes = remotes
43
+ @copies = copies
44
+ @address = address
45
+ @network = network
46
+ @history = []
47
+ @speed = []
48
+ @mutex = Mutex.new
49
+ @ledger = ledger
50
+ end
51
+
52
+ # Show its internals.
53
+ def to_json
54
+ {
55
+ 'ledger': File.exist?(@ledger) ? IO.read(@ledger).split("\n").count : 0
56
+ }
57
+ end
58
+
59
+ # Returns a list of modifed wallets (as Zold::Id)
60
+ def push(id, body, wallets, log)
61
+ start = Time.now
62
+ copies = Copies.new(File.join(@copies, id.to_s))
63
+ host = '0.0.0.0'
64
+ copies.add(body, host, Remotes::PORT, 0)
65
+ unless @remotes.all.empty?
66
+ Fetch.new(
67
+ wallets: wallets, remotes: @remotes, copies: copies.root, log: log
68
+ ).run(['fetch', id.to_s, "--ignore-node=#{@address}", "--network=#{@network}", '--quiet-if-absent'])
69
+ end
70
+ modified = merge(id, copies, wallets, log)
71
+ Clean.new(wallets: wallets, copies: copies.root, log: log).run(
72
+ ['clean', id.to_s, '--max-age=1']
73
+ )
74
+ copies.remove(host, Remotes::PORT)
75
+ if modified.empty?
76
+ log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and not modified anything")
77
+ else
78
+ log.info("Accepted #{id} in #{Age.new(start, limit: 1)} and modified #{modified.join(', ')}")
79
+ end
80
+ modified << id if copies.all.count > 1
81
+ modified
82
+ end
83
+
84
+ private
85
+
86
+ def merge(id, copies, wallets, log)
87
+ Tempfile.open do |f|
88
+ modified = Merge.new(
89
+ wallets: wallets, remotes: @remotes, copies: copies.root, log: log
90
+ ).run(['merge', id.to_s, "--ledger=#{f.path}", "--network=#{@network}"])
91
+ @mutex.synchronize do
92
+ txns = File.exist?(@ledger) ? IO.read(@ledger).strip.split("\n") : []
93
+ txns += IO.read(f.path).strip.split("\n")
94
+ IO.write(
95
+ @ledger,
96
+ txns.map { |t| t.split(';') }
97
+ .uniq { |t| "#{t[1]}-#{t[3]}" }
98
+ .reject { |t| Txn.parse_time(t[0]) < Time.now - 24 * 60 * 60 }
99
+ .map { |t| t.join(';') }
100
+ .join("\n")
101
+ .strip
102
+ )
103
+ end
104
+ modified
105
+ end
106
+ end
107
+ end
108
+ end
data/lib/zold/version.rb CHANGED
@@ -25,7 +25,7 @@
25
25
  # Copyright:: Copyright (c) 2018 Yegor Bugayenko
26
26
  # License:: MIT
27
27
  module Zold
28
- VERSION = '0.26.16'
28
+ VERSION = '0.26.17'
29
29
  PROTOCOL = 2
30
30
  REPO = 'zold-io/zold'
31
31
  end
@@ -29,6 +29,7 @@ require_relative '../../lib/zold/remotes'
29
29
  require_relative '../../lib/zold/id'
30
30
  require_relative '../../lib/zold/key'
31
31
  require_relative '../../lib/zold/node/entrance'
32
+ require_relative '../../lib/zold/node/pipeline'
32
33
  require_relative '../../lib/zold/commands/pay'
33
34
 
34
35
  # ENTRANCE test.
@@ -54,7 +55,11 @@ class TestEntrance < Zold::Test
54
55
  source = home.create_wallet(sid)
55
56
  target = home.create_wallet(tid)
56
57
  ledger = File.join(home.dir, 'ledger.csv')
57
- e = Zold::Entrance.new(home.wallets, home.remotes, home.copies(source).root, 'x', ledger: ledger, log: test_log)
58
+ e = Zold::Entrance.new(
59
+ home.wallets,
60
+ Zold::Pipeline.new(home.remotes, home.copies(source).root, 'x', ledger: ledger),
61
+ log: test_log
62
+ )
58
63
  modified = e.push(source.id, body)
59
64
  assert_equal(2, modified.count)
60
65
  assert_equal(Zold::Amount.new(zld: -19.99), source.balance)
@@ -68,7 +73,7 @@ class TestEntrance < Zold::Test
68
73
  def test_renders_json
69
74
  FakeHome.new(log: test_log).run do |home|
70
75
  wallet = home.create_wallet
71
- e = Zold::Entrance.new(home.wallets, home.remotes, home.copies.root, 'x', log: test_log)
76
+ e = Zold::Entrance.new(home.wallets, Zold::Pipeline.new(home.remotes, home.copies.root, 'x'), log: test_log)
72
77
  e.push(wallet.id, IO.read(wallet.path))
73
78
  assert(e.to_json[:history].include?(wallet.id.to_s))
74
79
  assert(!e.to_json[:speed].negative?)
@@ -139,7 +139,7 @@ class FrontTest < Zold::Test
139
139
  def test_increments_score
140
140
  FakeNode.new(log: test_log).run(opts('--threads=1')) do |port|
141
141
  3.times do |i|
142
- assert_equal_wait(true) do
142
+ assert_equal_wait(true, max: 60) do
143
143
  response = Zold::Http.new(uri: "http://localhost:#{port}/").get
144
144
  assert_equal(200, response.status, response.body)
145
145
  score = Zold::Score.parse_json(Zold::JsonPage.new(response.body).to_hash['score'])
@@ -26,6 +26,7 @@ require_relative 'fake_node'
26
26
  require_relative '../test__helper'
27
27
  require_relative '../../lib/zold/id'
28
28
  require_relative '../../lib/zold/node/entrance'
29
+ require_relative '../../lib/zold/node/pipeline'
29
30
  require_relative '../../lib/zold/node/spread_entrance'
30
31
  require_relative 'fake_entrance'
31
32
 
@@ -38,7 +39,11 @@ class TestSpreadEntrance < Zold::Test
38
39
  FakeHome.new(log: test_log).run do |home|
39
40
  wallet = home.create_wallet(Zold::Id.new)
40
41
  Zold::SpreadEntrance.new(
41
- Zold::Entrance.new(home.wallets, home.remotes, home.copies(wallet).root, 'x', log: test_log),
42
+ Zold::Entrance.new(
43
+ home.wallets,
44
+ Zold::Pipeline.new(home.remotes, home.copies(wallet).root, 'x'),
45
+ log: test_log
46
+ ),
42
47
  home.wallets, home.remotes, 'x', log: test_log
43
48
  ).start do |e|
44
49
  assert_equal(0, e.to_json[:modified])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zold
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.26.16
4
+ version: 0.26.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yegor Bugayenko
@@ -671,9 +671,10 @@ files:
671
671
  - lib/zold/node/farm.rb
672
672
  - lib/zold/node/farmers.rb
673
673
  - lib/zold/node/front.rb
674
- - lib/zold/node/journaled_entrance.rb
674
+ - lib/zold/node/journaled_pipeline.rb
675
675
  - lib/zold/node/nodup_entrance.rb
676
676
  - lib/zold/node/nospam_entrance.rb
677
+ - lib/zold/node/pipeline.rb
677
678
  - lib/zold/node/safe_entrance.rb
678
679
  - lib/zold/node/soft_error.rb
679
680
  - lib/zold/node/spread_entrance.rb
@@ -780,7 +781,7 @@ licenses:
780
781
  - MIT
781
782
  metadata: {}
782
783
  post_install_message: |-
783
- Thanks for installing Zold 0.26.16!
784
+ Thanks for installing Zold 0.26.17!
784
785
  Study our White Paper: https://papers.zold.io/wp.pdf
785
786
  Read our blog posts: https://blog.zold.io
786
787
  Try ZLD online wallet at: https://wts.zold.io