bitcoin-ruby 0.0.1 → 0.0.2

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.
Files changed (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. data/spec/bitcoin/storage_spec.rb +0 -229
@@ -1,3 +1,5 @@
1
+ # encoding: ascii-8bit
2
+
1
3
  Bitcoin.require_dependency :eventmachine
2
4
  Bitcoin.require_dependency :json
3
5
  require 'fileutils'
@@ -36,95 +38,130 @@ module Bitcoin::Network
36
38
  # clients to be notified for new block/tx events
37
39
  attr_reader :notifiers
38
40
 
39
- attr_reader :in_sync
41
+ # our external ip addresses we got told by peers
42
+ attr_accessor :external_ips
43
+
44
+ # time when the last main chain block was added
45
+ attr_reader :last_block_time
46
+
47
+ attr_accessor :relay_tx
48
+ attr_accessor :relay_propagation
49
+
40
50
 
41
51
  DEFAULT_CONFIG = {
52
+ :network => :bitcoin,
42
53
  :listen => ["0.0.0.0", Bitcoin.network[:default_port]],
43
54
  :connect => [],
44
- :command => "",
45
- :storage => Bitcoin::Storage.dummy({}),
46
- :headers_only => false,
55
+ :command => ["127.0.0.1", 9999],
56
+ :storage => "utxo::sqlite://~/.bitcoin-ruby/<network>/blocks.db",
57
+ :mode => :full,
47
58
  :dns => true,
48
- :epoll => false,
49
59
  :epoll_limit => 10000,
50
60
  :epoll_user => nil,
51
- :addr_file => "#{ENV['HOME']}/.bitcoin-ruby/addrs.json",
61
+ :addr_file => "~/.bitcoin-ruby/<network>/peers.json",
52
62
  :log => {
53
63
  :network => :info,
54
64
  :storage => :info,
55
65
  },
56
66
  :max => {
67
+ :connections_out => 8,
68
+ :connections_in => 32,
57
69
  :connections => 8,
58
70
  :addr => 256,
59
- :queue => 64,
60
- :inv => 128,
61
- :inv_cache => 1024,
71
+ :queue => 501,
72
+ :inv => 501,
73
+ :inv_cache => 0,
74
+ :unconfirmed => 100,
62
75
  },
63
76
  :intervals => {
64
- :queue => 5,
65
- :inv_queue => 5,
77
+ :queue => 1,
78
+ :inv_queue => 1,
66
79
  :addrs => 5,
67
- :connect => 15,
68
- :relay => 600,
80
+ :connect => 5,
81
+ :relay => 0,
69
82
  },
83
+ :import => nil,
84
+ :skip_validation => false,
85
+ :check_blocks => 1000,
70
86
  }
71
87
 
72
88
  def initialize config = {}
73
89
  @config = DEFAULT_CONFIG.deep_merge(config)
74
90
  @log = Bitcoin::Logger.create(:network, @config[:log][:network])
75
- @connections = []
76
- @command_connections = []
77
- @queue = []
78
- @queue_thread = nil
79
- @inv_queue = []
80
- @inv_queue_thread = nil
91
+ @connections, @command_connections = [], []
92
+ @queue, @queue_thread, @inv_queue, @inv_queue_thread = [], nil, [], nil
81
93
  set_store
82
94
  load_addrs
83
95
  @timers = {}
84
96
  @inv_cache = []
85
- @notifiers = Hash[[:block, :tx, :connection, :addr].map {|n| [n, EM::Channel.new]}]
86
- @in_sync = false
97
+ @notifiers = {}
98
+ @relay_propagation, @last_block_time, @external_ips = {}, Time.now, []
99
+ @unconfirmed, @relay_tx = {}, {}
87
100
  end
88
101
 
89
102
  def set_store
90
103
  backend, config = @config[:storage].split('::')
91
- @store = Bitcoin::Storage.send(backend, {:db => config}, ->(locator) {
104
+ @store = Bitcoin::Storage.send(backend, {
105
+ db: config, mode: @config[:mode], cache_head: true,
106
+ skip_validation: @config[:skip_validation],
107
+ log_level: @config[:log][:storage]}, ->(locator) {
92
108
  peer = @connections.select(&:connected?).sample
93
109
  peer.send_getblocks(locator)
94
110
  })
95
111
  @store.log.level = @config[:log][:storage]
112
+ @store.check_consistency(@config[:check_blocks])
113
+ if @config[:import]
114
+ @importing = true
115
+ EM.defer do
116
+ begin
117
+ @store.import(@config[:import]); @importing = false
118
+ rescue
119
+ log.fatal { $!.message }
120
+ puts *$@
121
+ stop
122
+ end
123
+ end
124
+ end
96
125
  end
97
126
 
98
127
  def load_addrs
99
- unless File.exist?(@config[:addr_file])
128
+ file = @config[:addr_file].sub("~", ENV["HOME"])
129
+ .sub("<network>", Bitcoin.network_name.to_s)
130
+ unless File.exist?(file)
100
131
  @addrs = []
132
+ FileUtils.mkdir_p(File.dirname(file))
101
133
  return
102
134
  end
103
- @addrs = JSON.load(File.read(@config[:addr_file])).map do |a|
135
+ @addrs = JSON.load(File.read(file)).map do |a|
104
136
  addr = Bitcoin::P::Addr.new
105
137
  addr.time, addr.service, addr.ip, addr.port =
106
138
  a['time'], a['service'], a['ip'], a['port']
107
139
  addr
108
140
  end
109
- log.info { "Initialized #{@addrs.size} addrs from #{@config[:addr_file]}." }
141
+ log.info { "Initialized #{@addrs.size} addrs from #{file}." }
142
+ rescue
143
+ @addrs = []
144
+ log.warn { "Error loading addrs from #{file}." }
110
145
  end
111
146
 
112
147
  def store_addrs
113
148
  return if !@addrs || !@addrs.any?
114
- file = @config[:addr_file]
149
+ file = @config[:addr_file].sub("~", ENV["HOME"])
150
+ .sub("<network>", Bitcoin.network_name.to_s)
115
151
  FileUtils.mkdir_p(File.dirname(file))
116
152
  File.open(file, 'w') do |f|
117
153
  addrs = @addrs.map {|a|
118
154
  Hash[[:time, :service, :ip, :port].zip(a.entries)] rescue nil }.compact
119
155
  f.write(JSON.pretty_generate(addrs))
120
156
  end
121
- log.info { "Stored #{@addrs.size} addrs to #{file}" }
157
+ log.info { "Stored #{@addrs.size} addrs to #{file}." }
122
158
  rescue
123
159
  log.warn { "Error storing addrs to #{file}." }
124
160
  end
125
161
 
126
162
  def stop
127
- log.info { "Shutting down..." }
163
+ puts "Shutting down..."
164
+ stop_timers
128
165
  EM.stop
129
166
  end
130
167
 
@@ -132,6 +169,30 @@ module Bitcoin::Network
132
169
  (Time.now - @started).to_i
133
170
  end
134
171
 
172
+ def start_timers
173
+ return EM.add_timer(1) { start_timers } if @importing
174
+ [:queue, :inv_queue, :addrs, :connect, :relay].each do |name|
175
+ interval = @config[:intervals][name].to_f
176
+ next if !interval || interval == 0.0
177
+ @timers[name] = EM.add_periodic_timer(interval, method("work_#{name}"))
178
+ end
179
+ end
180
+
181
+ def stop_timers
182
+ @timers.each {|n, t| EM.cancel_timer t }
183
+ end
184
+
185
+ # initiate epoll with given file descriptor and set effective user
186
+ def epoll_init
187
+ log.info { "EPOLL: Available file descriptors: " +
188
+ EM.set_descriptor_table_size(@config[:epoll_limit]).to_s }
189
+ if @config[:epoll_user]
190
+ EM.set_effective_user(@config[:epoll_user])
191
+ log.info { "EPOLL: Effective user set to: #{@config[:epoll_user]}" }
192
+ end
193
+ EM.epoll = true
194
+ end
195
+
135
196
  def run
136
197
  @started = Time.now
137
198
 
@@ -140,61 +201,90 @@ module Bitcoin::Network
140
201
  log.info { "Bye" }
141
202
  end
142
203
 
143
- init_epoll if @config[:epoll]
204
+ # enable kqueue (BSD, OS X)
205
+ if EM.kqueue?
206
+ log.info { 'Using BSD kqueue' }
207
+ EM.kqueue = true
208
+ end
209
+
210
+ # enable epoll (Linux)
211
+ if EM.epoll?
212
+ log.info { 'Using Linux epoll' }
213
+ epoll_init
214
+ end
144
215
 
145
216
  EM.run do
146
- [:addrs, :connect, :relay].each do |name|
147
- interval = @config[:intervals][name]
148
- next if !interval || interval == 0
149
- @timers[name] = EM.add_periodic_timer(interval, method("work_#{name}"))
150
- end
151
217
 
152
- if @config[:command]
153
- host, port = @config[:command]
218
+ start_timers
219
+
220
+ host, port = *@config[:command]
221
+ port ||= Bitcoin.network[:default_port]
222
+ if host
223
+ log.debug { "Trying to bind command socket to #{host}:#{port}" }
154
224
  EM.start_server(host, port, CommandHandler, self)
155
225
  log.info { "Command socket listening on #{host}:#{port}" }
156
226
  end
157
227
 
158
- if @config[:listen]
159
- host, port = @config[:listen]
160
- EM.start_server(host, port.to_i, ConnectionHandler, self, host, port.to_i)
228
+ host, port = *@config[:listen]
229
+ port ||= Bitcoin.network[:default_port]
230
+ if host
231
+ log.debug { "Trying to bind server socket to #{host}:#{port}" }
232
+ EM.start_server(host, port.to_i, ConnectionHandler, self, host, port.to_i, :in)
161
233
  log.info { "Server socket listening on #{host}:#{port}" }
162
234
  end
163
235
 
164
- if @config[:connect].any?
165
- @config[:connect].each{|host| connect_peer(*host) }
236
+ @config[:connect].each do |host, port|
237
+ port ||= Bitcoin.network[:default_port]
238
+ connect_peer(host, port)
239
+ log.info { "Connecting to #{host}:#{port}" }
166
240
  end
167
241
 
168
242
  work_connect if @addrs.any?
169
243
  connect_dns if @config[:dns]
170
- work_inv_queue
171
- work_queue
244
+
245
+ Signal.trap("INT") do
246
+ puts "Shutting down. You can force-quit by pressing Ctrl-C again, but it might corrupt your database!"
247
+ Signal.trap("INT") do
248
+ puts "Force Quit"
249
+ exit 1
250
+ end
251
+ self.stop
252
+ end
253
+
172
254
  end
173
255
  end
174
256
 
175
257
  # connect to peer at given +host+ / +port+
176
258
  def connect_peer host, port
177
- return if @connections.map{|c| c.host}.include?(host)
178
- log.info { "Attempting to connect to #{host}:#{port}" }
179
- EM.connect(host, port.to_i, ConnectionHandler, self, host, port.to_i)
259
+ return if @connections.map{|c| c.host }.include?(host)
260
+ log.debug { "Attempting to connect to #{host}:#{port}" }
261
+ EM.connect(host, port.to_i, ConnectionHandler, self, host, port.to_i, :out)
180
262
  rescue
181
- log.warn { "Error connecting to #{host}:#{port}" }
263
+ log.debug { "Error connecting to #{host}:#{port}" }
182
264
  log.debug { $!.inspect }
183
265
  end
184
266
 
185
267
  # query addrs from dns seed and connect
186
268
  def connect_dns
187
269
  unless Bitcoin.network[:dns_seeds].any?
188
- return log.warn { "No DNS seed nodes available" }
270
+ log.warn { "No DNS seed nodes available" }
271
+ return connect_known_peers
189
272
  end
190
273
  connect_dns_resolver(Bitcoin.network[:dns_seeds].sample) do |addrs|
191
274
  log.debug { "DNS returned addrs: #{addrs.inspect}" }
192
- addrs.sample(@config[:max][:connections] / 2).uniq.each do |addr|
275
+ addrs.sample(@config[:max][:connections_out] / 2).uniq.each do |addr|
193
276
  connect_peer(addr, Bitcoin.network[:default_port])
194
277
  end
195
278
  end
196
279
  end
197
280
 
281
+ def connect_known_peers
282
+ log.debug { "Attempting to connecting to known nodes" }
283
+ Bitcoin.network[:known_nodes].shuffle[0..3].each do |node|
284
+ connect_peer node, Bitcoin.network[:default_port]
285
+ end
286
+ end
287
+
198
288
  # get peer addrs from given dns +seed+ using em/dns_resolver.
199
289
  # fallback to using `nslookup` if it is not installed or fails.
200
290
  def connect_dns_resolver(seed)
@@ -225,7 +315,7 @@ module Bitcoin::Network
225
315
  # establish new ones if needed
226
316
  def work_connect
227
317
  log.debug { "Connect worker running" }
228
- desired = @config[:max][:connections] - @connections.size
318
+ desired = @config[:max][:connections_out] - @connections.select(&:outgoing?).size
229
319
  return if desired <= 0
230
320
  desired = 32 if desired > 32 # connect to max 32 peers at once
231
321
  if addrs.any?
@@ -246,9 +336,10 @@ module Bitcoin::Network
246
336
  peer = @connections.select(&:connected?).sample
247
337
  return unless peer
248
338
  log.info { "querying blocks from #{peer.host}:#{peer.port}" }
249
- if @config[:headers_only]
339
+ case @config[:mode]
340
+ when /lite/
250
341
  peer.send_getheaders locator unless @queue.size >= @config[:max][:queue]
251
- else
342
+ when /full|pruned/
252
343
  peer.send_getblocks locator unless @inv_queue.size >= @config[:max][:inv]
253
344
  end
254
345
  end
@@ -268,87 +359,121 @@ module Bitcoin::Network
268
359
  # check for new items in the queue and process them
269
360
  def work_queue
270
361
  @log.debug { "queue worker running" }
271
- EM.defer(nil, proc { work_queue }) do
272
- if @queue.size == 0
273
- getblocks if @inv_queue.size == 0 && !@in_sync
274
- sleep @config[:intervals][:queue]
362
+ return getblocks if @queue.size == 0
363
+
364
+ # switch off utxo cache once there aren't tons of new blocks coming in
365
+ if @store.in_sync?
366
+ if @store.is_a?(Bitcoin::Storage::Backends::UtxoStore) && @store.config[:utxo_cache] > 0
367
+ log.debug { "switching off utxo cache" }
368
+ @store.config[:utxo_cache] = 0
275
369
  end
276
- while obj = @queue.shift
277
- begin
278
- if @store.send("store_#{obj[0]}", obj[1])
279
- if obj[0].to_sym == :block
280
- block = @store.get_block(obj[1].hash)
281
- @notifiers[:block].push([obj[1], block.depth]) if block.chain == 0
282
- else
283
- @notifiers[:tx].push([obj[1]])
370
+ @config[:intervals].each do |name, value|
371
+ if value <= 1
372
+ log.debug { "setting #{name} interval to 5 seconds" }
373
+ @config[:intervals][name] = 5
374
+ end
375
+ end
376
+ end
377
+
378
+ while obj = @queue.shift
379
+ begin
380
+ if obj[0].to_sym == :block
381
+ if res = @store.send("new_#{obj[0]}", obj[1])
382
+ if res[1] == 0 && obj[1].hash == @store.get_head.hash
383
+ @last_block_time = Time.now
384
+ push_notification(:block, [obj[1], res[0]])
385
+ obj[1].tx.each {|tx| @unconfirmed.delete(tx.hash) }
386
+ end
387
+ getblocks if res[1] == 2 && @store.in_sync?
388
+ end
389
+ else
390
+ drop = @unconfirmed.size - @config[:max][:unconfirmed] + 1
391
+ drop.times { @unconfirmed.shift } if drop > 0
392
+ unless @unconfirmed[obj[1].hash]
393
+ @unconfirmed[obj[1].hash] = obj[1]
394
+ push_notification(:tx, [obj[1], 0])
395
+
396
+ if @notifiers[:output]
397
+ obj[1].out.each do |out|
398
+ address = Bitcoin::Script.new(out.pk_script).get_address
399
+ push_notification(:output, [obj[1].hash, address, out.value, 0])
400
+ end
284
401
  end
285
402
  end
286
- rescue
287
- @log.warn { $!.inspect }
288
- puts *$@
289
403
  end
404
+ rescue Bitcoin::Validation::ValidationError
405
+ @log.warn { "ValiationError storing #{obj[0]} #{obj[1].hash}: #{$!.message}" }
406
+ # File.open("./validation_error_#{obj[0]}_#{obj[1].hash}.bin", "w") {|f|
407
+ # f.write(obj[1].to_payload) }
408
+ # EM.stop
409
+ rescue
410
+ @log.warn { $!.inspect }
411
+ puts *$@
290
412
  end
291
- @in_sync = (@store.get_head && (Time.now - @store.get_head.time).to_i < 3600) ? true : false
292
413
  end
293
414
  end
294
415
 
295
416
  # check for new items in the inv queue and process them,
296
417
  # unless the queue is already full
297
418
  def work_inv_queue
298
- EM.defer(nil, proc { work_inv_queue }) do
299
- sleep @config[:intervals][:inv_queue] if @inv_queue.size == 0
300
- @log.debug { "inv queue worker running" }
301
- if @queue.size >= @config[:max][:queue]
302
- sleep @config[:intervals][:inv_queue]
303
- else
304
- while inv = @inv_queue.shift
305
- next if !@in_sync && inv[0] == :tx
306
- next if @queue.map{|i|i[1]}.map(&:hash).include?(inv[1])
307
- # next if @store.send("has_#{inv[0]}", inv[1])
308
- inv[2].send("send_getdata_#{inv[0]}", inv[1])
309
- end
310
- end
419
+ @log.debug { "inv queue worker running" }
420
+ return if @inv_queue.size == 0
421
+ return if @queue.size >= @config[:max][:queue]
422
+ while inv = @inv_queue.shift
423
+ next if !@store.in_sync? && inv[0] == :tx && @notifiers.empty?
424
+ next if @queue.map{|i|i[1]}.map(&:hash).include?(inv[1])
425
+ inv[2].send("send_getdata_#{inv[0]}", inv[1])
311
426
  end
312
427
  end
313
428
 
314
429
  # queue inv, caching the most current ones
315
430
  def queue_inv inv
316
- @inv_cache.shift(128) if @inv_cache.size > @config[:max][:inv_cache]
317
- return if @inv_cache.include?([inv[0], inv[1]]) ||
318
- @inv_queue.size >= @config[:max][:inv] ||
319
- (!@in_sync && inv[0] == :tx)
320
- @inv_cache << [inv[0], inv[1]]
321
- @inv_queue << inv
322
- end
431
+ hash = inv[1].unpack("H*")[0]
432
+ return if @inv_queue.include?(inv) || @queue.select {|i| i[1].hash == hash }.any?
323
433
 
434
+ return if @store.send("has_#{inv[0]}", hash)
324
435
 
325
- # initiate epoll with given file descriptor and set effective user
326
- def init_epoll
327
- log.info { "EPOLL: Available file descriptors: " +
328
- EM.set_descriptor_table_size(@config[:epoll_limit]).to_s }
329
- if @config[:epoll_user]
330
- EM.set_effective_user(@config[:epoll_user])
331
- log.info { "EPOLL: Effective user set to: #{@config[:epoll_user]}" }
332
- end
333
- EM.epoll
334
- end
335
-
336
- def relay_tx(tx)
337
- return false unless @in_sync
338
- @store.store_tx(tx)
339
- @connections.select(&:connected?).sample((@connections.size / 2) + 1).each do |peer|
340
- peer.send_inv(:tx, tx)
341
- end
436
+ # @inv_cache.shift(128) if @inv_cache.size > @config[:max][:inv_cache]
437
+ # return if @inv_cache.include?([inv[0], inv[1]]) ||
438
+ # @inv_queue.size >= @config[:max][:inv] ||
439
+ # (!@store.in_sync? && inv[0] == :tx)
440
+ # @inv_cache << [inv[0], inv[1]]
441
+ @inv_queue << inv
342
442
  end
343
443
 
344
444
  def work_relay
345
445
  log.debug { "relay worker running" }
346
446
  @store.get_unconfirmed_tx.each do |tx|
347
- log.info { "relaying tx #{tx.hash}" }
348
447
  relay_tx(tx)
349
448
  end
350
449
  end
351
450
 
451
+ # get the external ip that was suggested in version messages
452
+ # from other peers most often.
453
+ def external_ip
454
+ @external_ips.group_by(&:dup).values.max_by(&:size).first
455
+ rescue
456
+ @config[:listen][0]
457
+ end
458
+
459
+ # push notification +message+ to +channel+
460
+ def push_notification channel, message
461
+ @notifiers[channel.to_sym].push(message) if @notifiers[channel.to_sym]
462
+ end
463
+
464
+ # subscribe to notification +channel+.
465
+ # available channels are: block, tx, output, connection.
466
+ # see CommandHandler for details.
467
+ def subscribe channel
468
+ @notifiers[channel.to_sym] ||= EM::Channel.new
469
+ @notifiers[channel.to_sym].subscribe {|*data| yield(*data) }
470
+ end
471
+
472
+ # should the node accept new incoming connections?
473
+ def accept_connections?
474
+ connections.select(&:incoming?).size >= config[:max][:connections_in]
475
+ end
476
+
352
477
  end
353
478
  end
354
479