bitcoin-ruby 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.travis.yml +2 -7
  4. data/COPYING +1 -1
  5. data/Gemfile +2 -6
  6. data/Gemfile.lock +34 -0
  7. data/README.rdoc +16 -68
  8. data/Rakefile +3 -6
  9. data/bin/bitcoin_shell +0 -1
  10. data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
  11. data/lib/bitcoin.rb +350 -296
  12. data/lib/bitcoin/builder.rb +3 -1
  13. data/lib/bitcoin/connection.rb +2 -1
  14. data/lib/bitcoin/contracthash.rb +76 -0
  15. data/lib/bitcoin/dogecoin.rb +97 -0
  16. data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
  17. data/lib/bitcoin/ffi/openssl.rb +98 -2
  18. data/lib/bitcoin/ffi/secp256k1.rb +144 -0
  19. data/lib/bitcoin/key.rb +12 -2
  20. data/lib/bitcoin/logger.rb +3 -12
  21. data/lib/bitcoin/protocol/block.rb +3 -9
  22. data/lib/bitcoin/protocol/parser.rb +6 -2
  23. data/lib/bitcoin/protocol/tx.rb +44 -13
  24. data/lib/bitcoin/protocol/txin.rb +4 -2
  25. data/lib/bitcoin/protocol/txout.rb +2 -2
  26. data/lib/bitcoin/script.rb +212 -37
  27. data/lib/bitcoin/trezor/mnemonic.rb +130 -0
  28. data/lib/bitcoin/version.rb +1 -1
  29. data/spec/bitcoin/bitcoin_spec.rb +32 -3
  30. data/spec/bitcoin/builder_spec.rb +18 -0
  31. data/spec/bitcoin/contracthash_spec.rb +45 -0
  32. data/spec/bitcoin/dogecoin_spec.rb +176 -0
  33. data/spec/bitcoin/ffi_openssl.rb +45 -0
  34. data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
  35. data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
  36. data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
  37. data/spec/bitcoin/fixtures/coinbase.json +24 -0
  38. data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
  39. data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
  40. data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
  41. data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
  42. data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
  43. data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
  44. data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
  45. data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
  46. data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
  47. data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
  48. data/spec/bitcoin/protocol/block_spec.rb +0 -22
  49. data/spec/bitcoin/protocol/tx_spec.rb +145 -2
  50. data/spec/bitcoin/script/script_spec.rb +282 -0
  51. data/spec/bitcoin/secp256k1_spec.rb +48 -0
  52. data/spec/bitcoin/spec_helper.rb +0 -51
  53. data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
  54. metadata +48 -98
  55. data/bin/bitcoin_dns_seed +0 -130
  56. data/bin/bitcoin_gui +0 -80
  57. data/bin/bitcoin_node +0 -153
  58. data/bin/bitcoin_node_cli +0 -81
  59. data/bin/bitcoin_wallet +0 -402
  60. data/doc/CONFIG.rdoc +0 -66
  61. data/doc/EXAMPLES.rdoc +0 -13
  62. data/doc/NAMECOIN.rdoc +0 -34
  63. data/doc/NODE.rdoc +0 -225
  64. data/doc/STORAGE.rdoc +0 -33
  65. data/doc/WALLET.rdoc +0 -102
  66. data/examples/balance.rb +0 -66
  67. data/examples/forwarder.rb +0 -73
  68. data/examples/index_nhash.rb +0 -24
  69. data/examples/reindex_p2sh_addrs.rb +0 -44
  70. data/examples/relay_tx.rb +0 -22
  71. data/examples/verify_tx.rb +0 -57
  72. data/lib/bitcoin/config.rb +0 -58
  73. data/lib/bitcoin/gui/addr_view.rb +0 -44
  74. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  75. data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
  76. data/lib/bitcoin/gui/conn_view.rb +0 -38
  77. data/lib/bitcoin/gui/connection.rb +0 -70
  78. data/lib/bitcoin/gui/em_gtk.rb +0 -30
  79. data/lib/bitcoin/gui/gui.builder +0 -1643
  80. data/lib/bitcoin/gui/gui.rb +0 -292
  81. data/lib/bitcoin/gui/helpers.rb +0 -115
  82. data/lib/bitcoin/gui/tree_view.rb +0 -84
  83. data/lib/bitcoin/gui/tx_view.rb +0 -69
  84. data/lib/bitcoin/namecoin.rb +0 -280
  85. data/lib/bitcoin/network/command_client.rb +0 -104
  86. data/lib/bitcoin/network/command_handler.rb +0 -570
  87. data/lib/bitcoin/network/connection_handler.rb +0 -387
  88. data/lib/bitcoin/network/node.rb +0 -565
  89. data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
  90. data/lib/bitcoin/storage/models.rb +0 -171
  91. data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
  92. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
  93. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
  94. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
  95. data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
  96. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
  97. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
  98. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
  99. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
  100. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
  101. data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
  102. data/lib/bitcoin/storage/storage.rb +0 -517
  103. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
  104. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
  105. data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
  106. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
  107. data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
  108. data/lib/bitcoin/validation.rb +0 -400
  109. data/lib/bitcoin/wallet/coinselector.rb +0 -33
  110. data/lib/bitcoin/wallet/keygenerator.rb +0 -77
  111. data/lib/bitcoin/wallet/keystore.rb +0 -207
  112. data/lib/bitcoin/wallet/txdp.rb +0 -118
  113. data/lib/bitcoin/wallet/wallet.rb +0 -281
  114. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  115. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
  116. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  117. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
  118. data/spec/bitcoin/namecoin_spec.rb +0 -182
  119. data/spec/bitcoin/node/command_api_spec.rb +0 -663
  120. data/spec/bitcoin/storage/models_spec.rb +0 -104
  121. data/spec/bitcoin/storage/reorg_spec.rb +0 -236
  122. data/spec/bitcoin/storage/storage_spec.rb +0 -387
  123. data/spec/bitcoin/storage/validation_spec.rb +0 -300
  124. data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
  125. data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
  126. data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
  127. data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
  128. data/spec/bitcoin/wallet/wallet_spec.rb +0 -238
@@ -1,387 +0,0 @@
1
- # encoding: ascii-8bit
2
-
3
- require 'eventmachine'
4
-
5
- module Bitcoin::Network
6
-
7
- # Node network connection to a peer. Handles all the communication with a specific peer.
8
- class ConnectionHandler < EM::Connection
9
-
10
- LATENCY_MAX = (5*60*1000) # 5min in ms
11
-
12
- include Bitcoin
13
- include Bitcoin::Storage
14
-
15
- attr_reader :host, :port, :version, :direction
16
-
17
- # :new, :handshake, :connected, :disconnected
18
- attr_reader :state
19
-
20
- # latency of this connection based on last ping/pong
21
- attr_reader :latency_ms
22
-
23
- def log
24
- @log ||= Logger::LogWrapper.new("#@host:#@port", @node.log)
25
- end
26
-
27
- # how long has this connection been open?
28
- def uptime
29
- @started ? (Time.now - @started).to_i : 0
30
- end
31
-
32
- # create connection to +host+:+port+ for given +node+
33
- def initialize node, host, port, direction
34
- @node, @host, @port, @direction = node, host, port, direction
35
- @parser = Bitcoin::Protocol::Parser.new(self)
36
- @state = :new
37
- @version = nil
38
- @started = nil
39
- @port, @host = *Socket.unpack_sockaddr_in(get_peername) if get_peername
40
- @ping_nonce = nil
41
- @latency_ms = nil
42
- @lock = Monitor.new
43
- @last_getblocks = [] # the last few getblocks messages received
44
- rescue
45
- log.fatal { "Error in #initialize" }
46
- p $!; puts $@; exit
47
- end
48
-
49
- # check if connection is wanted, begin handshake if it is, disconnect if not
50
- def post_init
51
- if incoming?
52
- begin_handshake
53
- end
54
- rescue
55
- log.fatal { "Error in #post_init" }
56
- p $!; puts *$@
57
- end
58
-
59
- # only called for outgoing connection
60
- def connection_completed
61
- @connection_completed = true
62
- begin_handshake
63
- rescue
64
- log.fatal { "Error in #connection_completed" }
65
- p $!; puts *$@
66
- end
67
-
68
- # receive data from peer and invoke Protocol::Parser
69
- def receive_data data
70
- #log.debug { "Receiving data (#{data.size} bytes)" }
71
- @lock.synchronize { @parser.parse(data) }
72
- rescue
73
- log.warn { "Error handling data: #{data.hth}" }
74
- p $!; puts *$@
75
- end
76
-
77
- # connection closed; notify listeners and cleanup connection from node
78
- def unbind
79
- log.info { (outgoing? && !@connection_completed) ? "Connection failed" : "Disconnected" }
80
- @node.push_notification(:connection, {type: :disconnected, host: @host, port: @port})
81
- @state = :disconnected
82
- @node.connections.delete(self)
83
- end
84
-
85
- # begin handshake
86
- # TODO: disconnect if we don't complete within a reasonable time
87
- def begin_handshake
88
- if incoming? && !@node.accept_connections?
89
- return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
90
- end
91
- log.info { "Established #{@direction} connection" }
92
- @node.connections << self
93
- @state = :handshake
94
- send_version
95
- rescue
96
- log.fatal { "Error in #begin_handshake" }
97
- p $!; puts *$@
98
- end
99
-
100
- # complete handshake; set state, started time, notify listeners and add address to Node
101
- def complete_handshake
102
- if @state == :handshake
103
- log.debug { 'Handshake completed' }
104
- @state = :connected
105
- @started = Time.now
106
- @node.push_notification(:connection, info.merge(type: :connected))
107
- @node.addrs << addr
108
- end
109
- send_data P::Addr.pkt(@node.addr) if @node.config[:announce]
110
- end
111
-
112
- # received +inv_tx+ message for given +hash+.
113
- # add to inv_queue, unlesss maximum is reached
114
- def on_inv_transaction(hash)
115
- log.debug { ">> inv transaction: #{hash.hth}" }
116
- if @node.relay_propagation.keys.include?(hash.hth)
117
- @node.relay_propagation[hash.hth] += 1
118
- end
119
- return if @node.inv_queue.size >= @node.config[:max][:inv]
120
- @node.queue_inv([:tx, hash, self])
121
- end
122
-
123
- # received +inv_block+ message for given +hash+.
124
- # add to inv_queue, unless maximum is reached
125
- def on_inv_block(hash)
126
- log.debug { ">> inv block: #{hash.hth}" }
127
- return if @node.inv_queue.size >= @node.config[:max][:inv]
128
- @node.queue_inv([:block, hash, self])
129
- end
130
-
131
- # received +get_tx+ message for given +hash+.
132
- # send specified tx if we have it
133
- def on_get_transaction(hash)
134
- log.debug { ">> get transaction: #{hash.hth}" }
135
- tx = @node.store.get_tx(hash.hth)
136
- tx ||= @node.relay_tx[hash.hth]
137
- return unless tx
138
- pkt = Bitcoin::Protocol.pkt("tx", tx.to_payload)
139
- log.debug { "<< tx: #{tx.hash}" }
140
- send_data pkt
141
- end
142
-
143
- # received +get_block+ message for given +hash+.
144
- # send specified block if we have it
145
- def on_get_block(hash)
146
- log.debug { ">> get block: #{hash.hth}" }
147
- blk = @node.store.get_block(hash.hth)
148
- return unless blk
149
- pkt = Bitcoin::Protocol.pkt("block", blk.to_payload)
150
- log.debug { "<< block: #{blk.hash}" }
151
- send_data pkt
152
- end
153
-
154
- # received +addr+ message for given +addr+.
155
- # store addr in node and notify listeners
156
- def on_addr(addr)
157
- log.debug { ">> addr: #{addr.ip}:#{addr.port} alive: #{addr.alive?}, service: #{addr.service}" }
158
- @node.addrs << addr
159
- @node.push_notification(:addr, addr)
160
- end
161
-
162
- # received +tx+ message for given +tx+.
163
- # push tx to storage queue
164
- def on_tx(tx)
165
- log.debug { ">> tx: #{tx.hash} (#{tx.payload.size} bytes)" }
166
- @node.queue.push([:tx, tx])
167
- end
168
-
169
- # received +block+ message for given +blk+.
170
- # push block to storage queue
171
- def on_block(blk)
172
- log.debug { ">> block: #{blk.hash} (#{blk.payload.size} bytes)" }
173
- @node.queue.push([:block, blk])
174
- end
175
-
176
- # received +headers+ message for given +headers+.
177
- # push each header to storage queue
178
- def on_headers(headers)
179
- log.info { ">> headers (#{headers.size})" }
180
- headers.each {|h| @node.queue.push([:block, h])}
181
- end
182
-
183
- # received +version+ message for given +version+.
184
- # send +verack+ message and complete handshake
185
- def on_version(version)
186
- log.debug { ">> version: #{version.version}" }
187
- @node.external_ips << version.to.split(":")[0]
188
- @version = version
189
- log.debug { "<< verack" }
190
- send_data( Protocol.verack_pkt )
191
-
192
- # sometimes other nodes don't bother to send a verack back,
193
- # but we can consider the handshake complete once we sent ours.
194
- # apparently it can happen on incoming and outgoing connections alike
195
- complete_handshake
196
- end
197
-
198
- # received +verack+ message.
199
- # complete handshake if it isn't completed already
200
- def on_verack
201
- log.debug { ">> verack" }
202
- complete_handshake if outgoing?
203
- end
204
-
205
- # received +alert+ message for given +alert+.
206
- # TODO: implement alert logic, store, display, relay
207
- def on_alert(alert)
208
- log.warn { ">> alert: #{alert.inspect}" }
209
- end
210
-
211
- # received +getblocks+ message.
212
- # TODO: locator fallback
213
- def on_getblocks(version, hashes, stop_hash)
214
- # remember the last few received getblocks messages and ignore duplicate ones
215
- # fixes unexplained issue where remote node is bombarding us with the same getblocks
216
- # message over and over (probably related to missing locator fallback handling)
217
- return if @last_getblocks && @last_getblocks.include?([version, hashes, stop_hash])
218
- @last_getblocks << [version, hashes, stop_hash]
219
- @last_getblocks.shift if @last_getblocks.size > 3
220
-
221
- blk = @node.store.db[:blk][hash: hashes[0].htb.blob]
222
- depth = blk[:depth] if blk
223
- log.info { ">> getblocks #{hashes[0]} (#{depth || 'unknown'})" }
224
-
225
- return unless depth && depth <= @node.store.get_depth
226
- range = (depth+1..depth+500)
227
- blocks = @node.store.db[:blk].where(chain: 0, depth: range).order(:depth).select(:hash).all
228
- send_inv(:block, *blocks.map {|b| b[:hash].hth })
229
- end
230
-
231
- # received +getaddr+ message.
232
- # send +addr+ message with peer addresses back.
233
- def on_getaddr
234
- addrs = @node.config[:announce] ? [@node.addr] : []
235
- addrs += @node.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250]
236
- log.debug { "<< addr (#{addrs.size})" }
237
- send_data P::Addr.pkt(*addrs)
238
- end
239
-
240
- # begin handshake; send +version+ message
241
- def send_version
242
- from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
243
- version = Bitcoin::Protocol::Version.new({
244
- :version => 70001,
245
- :last_block => @node.store.get_depth,
246
- :from => from,
247
- :to => @host,
248
- :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
249
- #:user_agent => "/Satoshi:0.8.3/",
250
- })
251
- send_data(version.to_pkt)
252
- log.debug { "<< version: #{Bitcoin.network[:protocol_version]}" }
253
- end
254
-
255
- # send +inv+ message with given +type+ for given +obj+
256
- def send_inv type, *hashes
257
- hashes.each_slice(251) do |slice|
258
- pkt = Protocol.inv_pkt(type, slice.map(&:htb))
259
- log.debug { "<< inv #{type}: #{slice[0][0..16]}" + (slice.size > 1 ? "..#{slice[-1][0..16]}" : "") }
260
- send_data(pkt)
261
- end
262
- end
263
-
264
- # send +getdata tx+ message for given tx +hash+
265
- def send_getdata_tx(hash)
266
- pkt = Protocol.getdata_pkt(:tx, [hash])
267
- log.debug { "<< getdata tx: #{hash.hth}" }
268
- send_data(pkt)
269
- end
270
-
271
- # send +getdata block+ message for given block +hash+
272
- def send_getdata_block(hash)
273
- pkt = Protocol.getdata_pkt(:block, [hash])
274
- log.debug { "<< getdata block: #{hash.hth}" }
275
- send_data(pkt)
276
- end
277
-
278
- # send +getblocks+ message
279
- def send_getblocks locator = @node.store.get_locator
280
- if @node.store.get_depth == -1
281
- EM.add_timer(3) { send_getblocks }
282
- return get_genesis_block
283
- end
284
- pkt = Protocol.getblocks_pkt(@version.version, locator)
285
- log.info { "<< getblocks: #{locator.first}" }
286
- send_data(pkt)
287
- end
288
-
289
- # send +getheaders+ message
290
- def send_getheaders locator = @node.store.get_locator
291
- return get_genesis_block if @node.store.get_depth == -1
292
- pkt = Protocol.pkt("getheaders", [Bitcoin::network[:magic_head],
293
- locator.size.chr, *locator.map{|l| l.htb_reverse}, "\x00"*32].join)
294
- log.debug { "<< getheaders: #{locator.first}" }
295
- send_data(pkt)
296
- end
297
-
298
- # send +getaddr+ message
299
- def send_getaddr
300
- log.debug { "<< getaddr" }
301
- send_data(Protocol.pkt("getaddr", ""))
302
- end
303
-
304
- # send +ping+ message
305
- # TODO: wait for pong and disconnect if it doesn't arrive (and version is new enough)
306
- def send_ping
307
- if @version.version > Bitcoin::Protocol::BIP0031_VERSION
308
- @latency_ms = LATENCY_MAX
309
- @ping_nonce = rand(0xffffffff)
310
- @ping_time = Time.now
311
- log.debug { "<< ping (#{@ping_nonce})" }
312
- send_data(Protocol.ping_pkt(@ping_nonce))
313
- else
314
- # set latency to 5 seconds, terrible but this version should be obsolete now
315
- @latency_ms = (5*1000)
316
- log.debug { "<< ping" }
317
- send_data(Protocol.ping_pkt)
318
- end
319
- end
320
-
321
- # ask for the genesis block
322
- def get_genesis_block
323
- log.info { "Asking for genesis block" }
324
- pkt = Protocol.getdata_pkt(:block, [Bitcoin::network[:genesis_hash].htb])
325
- send_data(pkt)
326
- end
327
-
328
- # received +ping+ message with given +nonce+.
329
- # send +pong+ message back, if +nonce+ is set.
330
- # network versions <=60000 don't set the nonce and don't expect a pong.
331
- def on_ping nonce
332
- log.debug { ">> ping (#{nonce})" }
333
- send_data(Protocol.pong_pkt(nonce)) if nonce
334
- end
335
-
336
- # received +pong+ message with given +nonce+.
337
- def on_pong nonce
338
- if @ping_nonce == nonce
339
- @latency_ms = (Time.now - @ping_time) * 1000.0
340
- end
341
- log.debug { ">> pong (#{nonce}), latency: #{@latency_ms.to_i}ms" }
342
- end
343
-
344
- # begin handshake; send +version+ message
345
- def on_handshake_begin
346
- @state = :handshake
347
- from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
348
- version = Bitcoin::Protocol::Version.new({
349
- :version => 70001,
350
- :last_block => @node.store.get_depth,
351
- :from => from,
352
- :to => @host,
353
- :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
354
- #:user_agent => "/Satoshi:0.8.1/",
355
- })
356
- send_data(version.to_pkt)
357
- log.debug { "<< version (#{Bitcoin.network[:protocol_version]})" }
358
- end
359
-
360
- # get Addr object for this connection
361
- def addr
362
- return @addr if @addr
363
- @addr = P::Addr.new
364
- @addr.time, @addr.service, @addr.ip, @addr.port =
365
- Time.now.tv_sec, @version.services, @host, @port
366
- @addr
367
- end
368
-
369
- [:new, :handshake, :connected].each do |state|
370
- define_method("#{state}?") { @state == state }
371
- end
372
-
373
- # get info hash
374
- def info
375
- {
376
- :host => @host, :port => @port, :state => @state,
377
- :version => (@version.version rescue 0), :block => @version.last_block,
378
- :started => @started.to_i, :user_agent => @version.user_agent
379
- }
380
- end
381
-
382
- def incoming?; @direction == :in; end
383
- def outgoing?; @direction == :out; end
384
-
385
- end
386
-
387
- end
@@ -1,565 +0,0 @@
1
- # encoding: ascii-8bit
2
-
3
- Bitcoin.require_dependency :eventmachine
4
- Bitcoin.require_dependency :json
5
- require 'fileutils'
6
-
7
- module Bitcoin::Network
8
-
9
- class Node
10
-
11
- # configuration hash
12
- attr_reader :config
13
-
14
- # logger
15
- attr_reader :log
16
-
17
- # connections to other peers (Array of ConnectionHandler)
18
- attr_reader :connections
19
-
20
- # command connections (Array of CommandHandler)
21
- attr_reader :command_connections
22
-
23
- # storage queue (blocks/tx waiting to be stored)
24
- attr_reader :queue
25
-
26
- # inventory queue (blocks/tx waiting to be downloaded)
27
- attr_reader :inv_queue
28
-
29
- # inventory cache (blocks/tx recently downloaded)
30
- attr_reader :inv_cache
31
-
32
- # Bitcoin::Storage backend
33
- attr_reader :store
34
-
35
- # peer addrs (Array of Bitcoin::Protocol::Addr)
36
- attr_reader :addrs
37
-
38
- # clients to be notified for new block/tx events
39
- attr_reader :notifiers
40
-
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
-
50
-
51
- DEFAULT_CONFIG = {
52
- :network => :bitcoin,
53
- :listen => ["0.0.0.0", nil],
54
- :connect => [],
55
- :command => ["127.0.0.1", 9999],
56
- :storage => "utxo::sqlite://~/.bitcoin-ruby/<network>/blocks.db",
57
- :announce => false,
58
- :external_port => nil,
59
- :mode => :full,
60
- :cache_head => true,
61
- :index_nhash => false,
62
- :index_p2sh_type => false,
63
- :dns => true,
64
- :epoll_limit => 10000,
65
- :epoll_user => nil,
66
- :addr_file => "~/.bitcoin-ruby/<network>/peers.json",
67
- :log => {
68
- :network => :info,
69
- :storage => :info,
70
- },
71
- :max => {
72
- :connections_out => 8,
73
- :connections_in => 32,
74
- :connections => 8,
75
- :addr => 256,
76
- :queue => 501,
77
- :inv => 501,
78
- :inv_cache => 0,
79
- :unconfirmed => 100,
80
- },
81
- :intervals => {
82
- :queue => 1,
83
- :inv_queue => 1,
84
- :addrs => 5,
85
- :connect => 5,
86
- :relay => 0,
87
- },
88
- :import => nil,
89
- :skip_validation => false,
90
- :check_blocks => 1000,
91
- }
92
-
93
- def initialize config = {}
94
- @config = DEFAULT_CONFIG.deep_merge(config)
95
- @log = Bitcoin::Logger.create(:network, @config[:log][:network])
96
- @connections, @command_connections = [], []
97
- @queue, @queue_thread, @inv_queue, @inv_queue_thread = [], nil, [], nil
98
- set_store
99
- load_addrs
100
- @timers = {}
101
- @inv_cache = []
102
- @notifiers = {}
103
- @relay_propagation, @last_block_time, @external_ips = {}, Time.now, []
104
- @unconfirmed, @relay_tx = {}, {}
105
- end
106
-
107
- def set_store
108
- backend, config = @config[:storage].split('::')
109
- @store = Bitcoin::Storage.send(backend, {
110
- db: config, mode: @config[:mode], cache_head: @config[:cache_head],
111
- skip_validation: @config[:skip_validation], index_nhash: @config[:index_nhash],
112
- index_p2sh_type: @config[:index_p2sh_type],
113
- log_level: @config[:log][:storage]}, ->(locator) {
114
- peer = @connections.select(&:connected?).sample
115
- peer.send_getblocks(locator)
116
- })
117
- @store.log.level = @config[:log][:storage]
118
- @store.check_consistency(@config[:check_blocks])
119
- if @config[:import]
120
- @importing = true
121
- EM.defer do
122
- begin
123
- @store.import(@config[:import]); @importing = false
124
- rescue
125
- log.fatal { $!.message }
126
- puts *$@
127
- stop
128
- end
129
- end
130
- end
131
- end
132
-
133
- def load_addrs
134
- file = @config[:addr_file].sub("~", ENV["HOME"])
135
- .sub("<network>", Bitcoin.network_name.to_s)
136
- unless File.exist?(file)
137
- @addrs = []
138
- FileUtils.mkdir_p(File.dirname(file))
139
- return
140
- end
141
- @addrs = JSON.load(File.read(file)).map do |a|
142
- addr = Bitcoin::P::Addr.new
143
- addr.time, addr.service, addr.ip, addr.port =
144
- a['time'], a['service'], a['ip'], a['port']
145
- addr
146
- end
147
- log.info { "Initialized #{@addrs.size} addrs from #{file}." }
148
- rescue
149
- @addrs = []
150
- log.warn { "Error loading addrs from #{file}." }
151
- end
152
-
153
- def store_addrs
154
- return if !@addrs || !@addrs.any?
155
- file = @config[:addr_file].sub("~", ENV["HOME"])
156
- .sub("<network>", Bitcoin.network_name.to_s)
157
- FileUtils.mkdir_p(File.dirname(file))
158
- File.open(file, 'w') do |f|
159
- addrs = @addrs.map {|a|
160
- Hash[[:time, :service, :ip, :port].zip(a.entries)] rescue nil }.compact
161
- f.write(JSON.pretty_generate(addrs))
162
- end
163
- log.info { "Stored #{@addrs.size} addrs to #{file}." }
164
- rescue
165
- log.warn { "Error storing addrs to #{file}." }
166
- end
167
-
168
- def stop
169
- puts "Shutting down..."
170
- stop_timers
171
- EM.stop
172
- end
173
-
174
- def uptime
175
- (Time.now - @started).to_i
176
- end
177
-
178
- def start_timers
179
- return EM.add_timer(1) { start_timers } if @importing
180
- [:queue, :inv_queue, :addrs, :connect, :relay].each do |name|
181
- interval = @config[:intervals][name].to_f
182
- next if !interval || interval == 0.0
183
- @timers[name] = EM.add_periodic_timer(interval, method("work_#{name}"))
184
- end
185
- end
186
-
187
- def stop_timers
188
- @timers.each {|n, t| EM.cancel_timer t }
189
- end
190
-
191
- # initiate epoll with given file descriptor and set effective user
192
- def epoll_init
193
- log.info { "EPOLL: Available file descriptors: " +
194
- EM.set_descriptor_table_size(@config[:epoll_limit]).to_s }
195
- if @config[:epoll_user]
196
- EM.set_effective_user(@config[:epoll_user])
197
- log.info { "EPOLL: Effective user set to: #{@config[:epoll_user]}" }
198
- end
199
- EM.epoll = true
200
- end
201
-
202
- def run
203
- @started = Time.now
204
-
205
- EM.add_shutdown_hook do
206
- store_addrs
207
- log.info { "Bye" }
208
- end
209
-
210
- # enable kqueue (BSD, OS X)
211
- if EM.kqueue?
212
- log.info { 'Using BSD kqueue' }
213
- EM.kqueue = true
214
- end
215
-
216
- # enable epoll (Linux)
217
- if EM.epoll?
218
- log.info { 'Using Linux epoll' }
219
- epoll_init
220
- end
221
-
222
- EM.run do
223
-
224
- start_timers
225
-
226
- host, port = *@config[:command]
227
- port ||= Bitcoin.network[:default_port]
228
- if host
229
- log.debug { "Trying to bind command socket to #{host}:#{port}" }
230
- EM.start_server(host, port, CommandHandler, self)
231
- log.info { "Command socket listening on #{host}:#{port}" }
232
- end
233
-
234
- host, port = *@config[:listen]
235
- port ||= Bitcoin.network[:default_port]
236
- if host
237
- log.debug { "Trying to bind server socket to #{host}:#{port}" }
238
- EM.start_server(host, port.to_i, ConnectionHandler, self, host, port.to_i, :in)
239
- log.info { "Server socket listening on #{host}:#{port}" }
240
- end
241
-
242
- @config[:connect].each do |host, port|
243
- port ||= Bitcoin.network[:default_port]
244
- connect_peer(host, port)
245
- log.info { "Connecting to #{host}:#{port}" }
246
- end
247
-
248
- work_connect if @addrs.any?
249
- connect_dns if @config[:dns]
250
-
251
- Signal.trap("INT") do
252
- puts "Shutting down. You can force-quit by pressing Ctrl-C again, but it might corrupt your database!"
253
- Signal.trap("INT") do
254
- puts "Force Quit"
255
- exit 1
256
- end
257
- self.stop
258
- end
259
-
260
- subscribe(:block) do |blk, depth|
261
- next unless @store.in_sync?
262
- @log.debug { "Relaying block #{blk.hash}" }
263
- @connections.each do |conn|
264
- next unless conn.connected?
265
- conn.send_inv(:block, blk.hash)
266
- end
267
- end
268
-
269
- @store.subscribe(:block) do |blk, depth, chain|
270
- if chain == 0 && blk.hash == @store.get_head.hash
271
- @last_block_time = Time.now
272
- push_notification(:block, [blk, depth])
273
- blk.tx.each {|tx| @unconfirmed.delete(tx.hash) }
274
- end
275
- getblocks if chain == 2 && @store.in_sync?
276
- end
277
-
278
- @store.subscribe(:reorg) do |new_main, new_side|
279
- @log.warn { "Reorg of #{new_side.size} blocks." }
280
- new_main.each {|b| @log.debug { "new main: #{b}" } }
281
- new_side.each {|b| @log.debug { "new side: #{b}" } }
282
- push_notification(:reorg, [new_main, new_side])
283
- end
284
-
285
- end
286
- end
287
-
288
- # connect to peer at given +host+ / +port+
289
- def connect_peer host, port
290
- return if @connections.map{|c| c.host }.include?(host)
291
- log.debug { "Attempting to connect to #{host}:#{port}" }
292
- EM.connect(host, port.to_i, ConnectionHandler, self, host, port.to_i, :out)
293
- rescue
294
- log.debug { "Error connecting to #{host}:#{port}" }
295
- log.debug { $!.inspect }
296
- end
297
-
298
- # query addrs from dns seed and connect
299
- def connect_dns
300
- unless Bitcoin.network[:dns_seeds].any?
301
- log.warn { "No DNS seed nodes available" }
302
- return connect_known_peers
303
- end
304
- connect_dns_resolver(Bitcoin.network[:dns_seeds].sample) do |addrs|
305
- log.debug { "DNS returned addrs: #{addrs.inspect}" }
306
- addrs.sample(@config[:max][:connections_out] / 2).uniq.each do |addr|
307
- connect_peer(addr, Bitcoin.network[:default_port])
308
- end
309
- end
310
- end
311
-
312
- def connect_known_peers
313
- log.debug { "Attempting to connecting to known nodes" }
314
- Bitcoin.network[:known_nodes].shuffle[0..3].each do |node|
315
- connect_peer node, Bitcoin.network[:default_port]
316
- end
317
- end
318
-
319
- # get peer addrs from given dns +seed+ using em/dns_resolver.
320
- # fallback to using `nslookup` if it is not installed or fails.
321
- def connect_dns_resolver(seed)
322
- if Bitcoin.require_dependency "em/dns_resolver", gem: "em-dns", exit: false
323
- log.info { "Querying addresses from DNS seed: #{seed}" }
324
-
325
- dns = EM::DnsResolver.resolve(seed)
326
- dns.callback {|addrs| yield(addrs) }
327
- dns.errback do |*a|
328
- log.error { "Cannot resolve DNS seed #{seed}: #{a.inspect}" }
329
- connect_dns_nslookup(Bitcoin.network[:dns_seeds].sample) {|a| yield(a) }
330
- end
331
- else
332
- log.info { "Falling back to nslookup resolver." }
333
- connect_dns_nslookup(seed) {|a| yield(a) }
334
- end
335
- end
336
-
337
- # get peers from dns via nslookup
338
- def connect_dns_nslookup(seed)
339
- log.info { "Querying addresses from DNS seed: #{seed}" }
340
- addrs = `nslookup #{seed}`.scan(/Address\: (.+)$/).flatten
341
- # exit if @config[:dns] && hosts.size == 0
342
- yield(addrs)
343
- end
344
-
345
- # check if there are enough connections and try to
346
- # establish new ones if needed
347
- def work_connect
348
- log.debug { "Connect worker running" }
349
- desired = @config[:max][:connections_out] - @connections.select(&:outgoing?).size
350
- return if desired <= 0
351
- desired = 32 if desired > 32 # connect to max 32 peers at once
352
- if addrs.any?
353
- addrs.sample(desired) do |addr|
354
- Time.now.tv_sec + 10800 - addr.time
355
- end.each do |addr|
356
- connect_peer(addr.ip, addr.port)
357
- end
358
- elsif @config[:dns]
359
- connect_dns
360
- end
361
- rescue
362
- log.error { "Error during connect: #{$!.inspect}" }
363
- end
364
-
365
- # query blocks from random peer
366
- def getblocks locator = store.get_locator
367
- peer = @connections.select(&:connected?).sample
368
- return unless peer
369
- log.info { "querying blocks from #{peer.host}:#{peer.port}" }
370
- case @config[:mode]
371
- when /lite/
372
- peer.send_getheaders locator unless @queue.size >= @config[:max][:queue]
373
- when /full|pruned/
374
- peer.send_getblocks locator unless @inv_queue.size >= @config[:max][:inv]
375
- end
376
- end
377
-
378
- # check if the addr store is full and request new addrs
379
- # from a random peer if it isn't
380
- def work_addrs
381
- log.debug { "addr worker running" }
382
- @addrs.delete_if{|addr| !addr.alive? } if @addrs.size >= @config[:max][:addr]
383
- return if !@connections.any? || @config[:max][:connections] <= @connections.size
384
- connections = @connections.select(&:connected?)
385
- return unless connections.any?
386
- log.info { "requesting addrs" }
387
- connections.sample.send_getaddr
388
- end
389
-
390
- # check for new items in the queue and process them
391
- def work_queue
392
- @log.debug { "queue worker running" }
393
- return getblocks if @queue.size == 0
394
-
395
- # switch off utxo cache once there aren't tons of new blocks coming in
396
- if @store.in_sync?
397
- if @store.is_a?(Bitcoin::Storage::Backends::UtxoStore) && @store.config[:utxo_cache] > 0
398
- log.debug { "switching off utxo cache" }
399
- @store.config[:utxo_cache] = 0
400
- end
401
- @config[:intervals].each do |name, value|
402
- if value <= 1
403
- log.debug { "setting #{name} interval to 5 seconds" }
404
- @config[:intervals][name] = 5
405
- end
406
- end
407
- end
408
-
409
- while obj = @queue.shift
410
- begin
411
- if obj[0].to_sym == :block
412
- @store.new_block(obj[1])
413
- else
414
- drop = @unconfirmed.size - @config[:max][:unconfirmed] + 1
415
- drop.times { @unconfirmed.shift } if drop > 0
416
- unless @unconfirmed[obj[1].hash]
417
- @unconfirmed[obj[1].hash] = obj[1]
418
- push_notification(:tx, [obj[1], 0])
419
- end
420
- end
421
- rescue Bitcoin::Validation::ValidationError
422
- @log.warn { "ValidationError storing #{obj[0]} #{obj[1].hash}: #{$!.message}" }
423
- # File.open("./validation_error_#{obj[0]}_#{obj[1].hash}.bin", "w") {|f|
424
- # f.write(obj[1].to_payload) }
425
- # EM.stop
426
- rescue
427
- @log.warn { $!.inspect }
428
- puts *$@
429
- end
430
- end
431
- end
432
-
433
- # check for new items in the inv queue and process them,
434
- # unless the queue is already full
435
- def work_inv_queue
436
- @log.debug { "inv queue worker running" }
437
- return if @inv_queue.size == 0
438
- return if @queue.size >= @config[:max][:queue]
439
- while inv = @inv_queue.shift
440
- next if !@store.in_sync? && inv[0] == :tx && @notifiers.empty?
441
- next if @queue.map{|i|i[1]}.map(&:hash).include?(inv[1])
442
- inv[2].send("send_getdata_#{inv[0]}", inv[1])
443
- end
444
- end
445
-
446
- # queue inv, caching the most current ones
447
- def queue_inv inv
448
- hash = inv[1].unpack("H*")[0]
449
- return if @inv_queue.include?(inv) || @queue.select {|i| i[1].hash == hash }.any?
450
-
451
- return if @store.send("has_#{inv[0]}", hash)
452
-
453
- # @inv_cache.shift(128) if @inv_cache.size > @config[:max][:inv_cache]
454
- # return if @inv_cache.include?([inv[0], inv[1]]) ||
455
- # @inv_queue.size >= @config[:max][:inv] ||
456
- # (!@store.in_sync? && inv[0] == :tx)
457
- # @inv_cache << [inv[0], inv[1]]
458
- @inv_queue << inv
459
- end
460
-
461
- def work_relay
462
- log.debug { "relay worker running" }
463
- @store.get_unconfirmed_tx.each do |tx|
464
- relay_tx(tx)
465
- end
466
- end
467
-
468
- # get the external ip that was suggested in version messages
469
- # from other peers most often.
470
- def external_ip
471
- @external_ips.group_by(&:dup).values.max_by(&:size).first
472
- rescue
473
- @config[:listen][0]
474
- end
475
-
476
- # get the external port
477
- # assume the same as local port if config option :external_port isn't given explicitly
478
- def external_port
479
- @config[:external_port] || @config[:listen][1] || Bitcoin.network[:default_port]
480
- end
481
-
482
- # push notification +message+ to +channel+
483
- def push_notification channel, message
484
- @notifiers[channel.to_sym].push(message) if @notifiers[channel.to_sym]
485
- end
486
-
487
- # subscribe to notification +channel+.
488
- # available channels are: block, tx, output, connection.
489
- # see CommandHandler for details.
490
- def subscribe channel
491
- @notifiers[channel.to_sym] ||= EM::Channel.new
492
- @notifiers[channel.to_sym].subscribe do |*data|
493
- begin
494
- yield(*data)
495
- rescue
496
- p $!; puts *$@
497
- end
498
- end
499
- end
500
-
501
- def unsubscribe channel, id
502
- @notifiers[channel.to_sym].unsubscribe(id)
503
- end
504
-
505
- # should the node accept new incoming connections?
506
- def accept_connections?
507
- connections.select(&:incoming?).size < config[:max][:connections_in]
508
- end
509
-
510
- # get Addr object for our own server
511
- def addr
512
- @addr = Bitcoin::P::Addr.new
513
- @addr.time, @addr.service, @addr.ip, @addr.port =
514
- Time.now.tv_sec, (1 << 0), external_ip, external_port
515
- @addr
516
- end
517
-
518
- end
519
- end
520
-
521
- class Array
522
- def random(weights=nil)
523
- return random(map {|n| yield(n) }) if block_given?
524
- return random(map {|n| n.send(weights) }) if weights.is_a? Symbol
525
-
526
- weights ||= Array.new(length, 1.0)
527
- total = weights.inject(0.0) {|t,w| t+w}
528
- point = rand * total
529
-
530
- zip(weights).each do |n,w|
531
- return n if w >= point
532
- point -= w
533
- end
534
- end
535
-
536
- def weighted_sample(n, weights = nil)
537
- src = dup
538
- buf = []
539
- n = src.size if n > src.size
540
- while buf.size < n
541
- if block_given?
542
- item = src.random {|n| yield(n) }
543
- else
544
- item = src.random(weights)
545
- end
546
- buf << item; src.delete(item)
547
- end
548
- buf
549
- end
550
-
551
- class ::Hash
552
- def deep_merge(hash)
553
- target = dup
554
- hash.keys.each do |key|
555
- if hash[key].is_a? Hash and self[key].is_a? Hash
556
- target[key] = target[key].deep_merge(hash[key])
557
- next
558
- end
559
- target[key] = hash[key]
560
- end
561
- target
562
- end
563
- end
564
-
565
- end