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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.travis.yml +2 -7
- data/COPYING +1 -1
- data/Gemfile +2 -6
- data/Gemfile.lock +34 -0
- data/README.rdoc +16 -68
- data/Rakefile +3 -6
- data/bin/bitcoin_shell +0 -1
- data/{concept-examples/blockchain-pow.rb → examples/concept-blockchain-pow.rb} +0 -0
- data/lib/bitcoin.rb +350 -296
- data/lib/bitcoin/builder.rb +3 -1
- data/lib/bitcoin/connection.rb +2 -1
- data/lib/bitcoin/contracthash.rb +76 -0
- data/lib/bitcoin/dogecoin.rb +97 -0
- data/lib/bitcoin/ffi/bitcoinconsensus.rb +74 -0
- data/lib/bitcoin/ffi/openssl.rb +98 -2
- data/lib/bitcoin/ffi/secp256k1.rb +144 -0
- data/lib/bitcoin/key.rb +12 -2
- data/lib/bitcoin/logger.rb +3 -12
- data/lib/bitcoin/protocol/block.rb +3 -9
- data/lib/bitcoin/protocol/parser.rb +6 -2
- data/lib/bitcoin/protocol/tx.rb +44 -13
- data/lib/bitcoin/protocol/txin.rb +4 -2
- data/lib/bitcoin/protocol/txout.rb +2 -2
- data/lib/bitcoin/script.rb +212 -37
- data/lib/bitcoin/trezor/mnemonic.rb +130 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +32 -3
- data/spec/bitcoin/builder_spec.rb +18 -0
- data/spec/bitcoin/contracthash_spec.rb +45 -0
- data/spec/bitcoin/dogecoin_spec.rb +176 -0
- data/spec/bitcoin/ffi_openssl.rb +45 -0
- data/spec/bitcoin/fixtures/156e6e1b84c5c3bd3a0927b25e4119fadce6e6d5186f363317511d1d680fae9a.json +24 -0
- data/spec/bitcoin/fixtures/8d0b238a06b5a70be75d543902d02d7a514d68d3252a949a513865ac3538874c.json +24 -0
- data/spec/bitcoin/fixtures/coinbase-toshi.json +33 -0
- data/spec/bitcoin/fixtures/coinbase.json +24 -0
- data/spec/bitcoin/fixtures/dogecoin-block-60323982f9c5ff1b5a954eac9dc1269352835f47c2c5222691d80f0d50dcf053.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-02-toshi.json +46 -0
- data/spec/bitcoin/fixtures/rawtx-03-toshi.json +73 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-04fdc38d6722ab4b12d79113fc4b2896bdcc5169710690ee4e78541b98e467b4.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-0b294c7d11dd21bcccb8393e6744fed7d4d1981a08c00e3e88838cc421f33c9f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-3bc52ac063291ad92d95ddda5fd776a342083b95607ad32ed8bc6f8f7d30449e.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-6f0bbdd4e71a8af4305018d738184df32dbb6f27284fdebd5b56d16947f7c181.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a7c9b06e275e8674cc19a5f7d3e557c72c6d93576e635b33212dbe08ab7cdb60.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-f80acbd2f594d04ddb0e1cacba662132104909157dff526935a3c88abe9201a5.bin +0 -0
- data/spec/bitcoin/protocol/block_spec.rb +0 -22
- data/spec/bitcoin/protocol/tx_spec.rb +145 -2
- data/spec/bitcoin/script/script_spec.rb +282 -0
- data/spec/bitcoin/secp256k1_spec.rb +48 -0
- data/spec/bitcoin/spec_helper.rb +0 -51
- data/spec/bitcoin/trezor/mnemonic_spec.rb +161 -0
- metadata +48 -98
- data/bin/bitcoin_dns_seed +0 -130
- data/bin/bitcoin_gui +0 -80
- data/bin/bitcoin_node +0 -153
- data/bin/bitcoin_node_cli +0 -81
- data/bin/bitcoin_wallet +0 -402
- data/doc/CONFIG.rdoc +0 -66
- data/doc/EXAMPLES.rdoc +0 -13
- data/doc/NAMECOIN.rdoc +0 -34
- data/doc/NODE.rdoc +0 -225
- data/doc/STORAGE.rdoc +0 -33
- data/doc/WALLET.rdoc +0 -102
- data/examples/balance.rb +0 -66
- data/examples/forwarder.rb +0 -73
- data/examples/index_nhash.rb +0 -24
- data/examples/reindex_p2sh_addrs.rb +0 -44
- data/examples/relay_tx.rb +0 -22
- data/examples/verify_tx.rb +0 -57
- data/lib/bitcoin/config.rb +0 -58
- data/lib/bitcoin/gui/addr_view.rb +0 -44
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +0 -80
- data/lib/bitcoin/gui/conn_view.rb +0 -38
- data/lib/bitcoin/gui/connection.rb +0 -70
- data/lib/bitcoin/gui/em_gtk.rb +0 -30
- data/lib/bitcoin/gui/gui.builder +0 -1643
- data/lib/bitcoin/gui/gui.rb +0 -292
- data/lib/bitcoin/gui/helpers.rb +0 -115
- data/lib/bitcoin/gui/tree_view.rb +0 -84
- data/lib/bitcoin/gui/tx_view.rb +0 -69
- data/lib/bitcoin/namecoin.rb +0 -280
- data/lib/bitcoin/network/command_client.rb +0 -104
- data/lib/bitcoin/network/command_handler.rb +0 -570
- data/lib/bitcoin/network/connection_handler.rb +0 -387
- data/lib/bitcoin/network/node.rb +0 -565
- data/lib/bitcoin/storage/dummy/dummy_store.rb +0 -179
- data/lib/bitcoin/storage/models.rb +0 -171
- data/lib/bitcoin/storage/sequel/migrations.rb +0 -99
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +0 -45
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/004_change_txin_prev_out_to_blob.rb +0 -18
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +0 -14
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +0 -16
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +0 -31
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +0 -56
- data/lib/bitcoin/storage/sequel/sequel_store.rb +0 -551
- data/lib/bitcoin/storage/storage.rb +0 -517
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +0 -52
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +0 -18
- data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +0 -14
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +0 -14
- data/lib/bitcoin/storage/utxo/utxo_store.rb +0 -374
- data/lib/bitcoin/validation.rb +0 -400
- data/lib/bitcoin/wallet/coinselector.rb +0 -33
- data/lib/bitcoin/wallet/keygenerator.rb +0 -77
- data/lib/bitcoin/wallet/keystore.rb +0 -207
- data/lib/bitcoin/wallet/txdp.rb +0 -118
- data/lib/bitcoin/wallet/wallet.rb +0 -281
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +0 -43
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +0 -67
- data/spec/bitcoin/namecoin_spec.rb +0 -182
- data/spec/bitcoin/node/command_api_spec.rb +0 -663
- data/spec/bitcoin/storage/models_spec.rb +0 -104
- data/spec/bitcoin/storage/reorg_spec.rb +0 -236
- data/spec/bitcoin/storage/storage_spec.rb +0 -387
- data/spec/bitcoin/storage/validation_spec.rb +0 -300
- data/spec/bitcoin/wallet/coinselector_spec.rb +0 -38
- data/spec/bitcoin/wallet/keygenerator_spec.rb +0 -69
- data/spec/bitcoin/wallet/keystore_spec.rb +0 -190
- data/spec/bitcoin/wallet/txdp_spec.rb +0 -76
- 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
|
data/lib/bitcoin/network/node.rb
DELETED
|
@@ -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
|