bitcoin-ruby 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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