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