bitcoin-ruby 0.0.6 → 0.0.7

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