bitcoin-ruby 0.0.1 → 0.0.2
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.
- 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
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
1
5
|
# Client to connect to CommandHandler and issue requests or register for events
|
|
2
6
|
class Bitcoin::Network::CommandClient < EM::Connection
|
|
3
7
|
|
|
@@ -35,11 +39,7 @@ class Bitcoin::Network::CommandClient < EM::Connection
|
|
|
35
39
|
def unbind
|
|
36
40
|
log.debug { "Disconnected." }
|
|
37
41
|
callback :disconnected
|
|
38
|
-
|
|
39
|
-
log.info { "Trying to start server..." }
|
|
40
|
-
EM.defer { system("bin/bitcoin_node", "--quiet") }
|
|
41
|
-
end
|
|
42
|
-
EM.add_timer(1) do
|
|
42
|
+
EM.add_timer(@connection_attempts) do
|
|
43
43
|
@connection_attempts += 1
|
|
44
44
|
reconnect(@host, @port)
|
|
45
45
|
post_init
|
|
@@ -86,7 +86,8 @@ class Bitcoin::Network::CommandClient < EM::Connection
|
|
|
86
86
|
# register callbacks for monitor
|
|
87
87
|
def register_monitor_callbacks
|
|
88
88
|
on_monitor do |type, data|
|
|
89
|
-
|
|
89
|
+
type, *params = type.split("_")
|
|
90
|
+
callback(type, *((data || []) + (params || [])))
|
|
90
91
|
end
|
|
91
92
|
end
|
|
92
93
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'monitor'
|
|
3
5
|
|
|
@@ -29,83 +31,156 @@ class Bitcoin::Network::CommandHandler < EM::Connection
|
|
|
29
31
|
# receive request from the client
|
|
30
32
|
def receive_data data
|
|
31
33
|
@buf.extract(data).each do |packet|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
begin
|
|
35
|
+
cmd, args = JSON::parse(packet)
|
|
36
|
+
log.debug { [cmd, args] }
|
|
37
|
+
if cmd == "relay_tx"
|
|
38
|
+
handle_relay_tx(*args)
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
if respond_to?("handle_#{cmd}")
|
|
42
|
+
respond(cmd, send("handle_#{cmd}", *args))
|
|
43
|
+
else
|
|
44
|
+
respond(cmd, { error: "unknown command: #{cmd}. send 'help' for help." })
|
|
45
|
+
end
|
|
46
|
+
rescue ArgumentError
|
|
47
|
+
respond(cmd, { error: $!.message })
|
|
38
48
|
end
|
|
39
49
|
end
|
|
40
50
|
rescue Exception
|
|
41
|
-
p
|
|
51
|
+
p $!; puts *$@
|
|
42
52
|
end
|
|
43
53
|
|
|
44
54
|
# handle +monitor+ command; subscribe client to specified channels
|
|
45
|
-
# (+block+, +tx+, +connection+)
|
|
55
|
+
# (+block+, +tx+, +output+, +connection+).
|
|
56
|
+
# Some commands can have parameters, e.g. the number of confirmations
|
|
57
|
+
# +tx+ or +output+ should have. Parameters are appended to the command
|
|
58
|
+
# name after an underscore (_), e.g. subscribe to channel "tx_6" to
|
|
59
|
+
# receive only transactions with 6 confirmations.
|
|
60
|
+
#
|
|
61
|
+
# Receive new blocks:
|
|
46
62
|
# bitcoin_node monitor block
|
|
47
|
-
#
|
|
63
|
+
# Receive new (unconfirmed) transactions:
|
|
64
|
+
# bitcoin_node monitor tx
|
|
65
|
+
# Receive transactions with 6 confirmations:
|
|
66
|
+
# bitcoin_node monitor tx_6
|
|
67
|
+
# Receive [txhash, address, value] for each output:
|
|
68
|
+
# bitcoin_node monitor output
|
|
69
|
+
# Receive peer connections/disconnections:
|
|
70
|
+
# bitcoin_node monitor connection"
|
|
71
|
+
# Combine multiple channels:
|
|
72
|
+
# bitcoin_node monitor "block tx tx_1 tx_6 connection"
|
|
73
|
+
#
|
|
74
|
+
# NOTE: When a new block is found, it might include transactions that we
|
|
75
|
+
# didn't previously receive as unconfirmed. To make sure you receive all
|
|
76
|
+
# transactions, also subscribe to the tx_1 channel.
|
|
48
77
|
def handle_monitor *channels
|
|
49
|
-
channels.each do |channel|
|
|
50
|
-
@node.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
channels.map(&:to_sym).each do |channel|
|
|
79
|
+
@node.subscribe(channel) {|*data| respond("monitor", [channel, *data]) }
|
|
80
|
+
name, *params = channel.to_s.split("_")
|
|
81
|
+
send("handle_monitor_#{name}", *params)
|
|
82
|
+
log.info { "Client subscribed to channel #{channel}" }
|
|
83
|
+
end
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Handle +monitor block+ command; send the current chain head
|
|
88
|
+
# after client is subscribed to :block channel
|
|
89
|
+
def handle_monitor_block
|
|
90
|
+
head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
|
|
91
|
+
respond("monitor", ["block", [head, @node.store.get_depth]]) if head
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Handle +monitor tx+ command.
|
|
95
|
+
# When +conf+ is given, don't subscribe to the :tx channel for unconfirmed
|
|
96
|
+
# transactions. Instead, subscribe to the :block channel, and whenever a new
|
|
97
|
+
# block comes in, send all transactions that now have +conf+ confirmations.
|
|
98
|
+
def handle_monitor_tx conf = nil
|
|
99
|
+
return unless conf
|
|
100
|
+
if conf.to_i == 0 # 'tx_0' is just an alias for 'tx'
|
|
101
|
+
return @node.subscribe(:tx) {|*a| @node.notifiers[:tx_0].push(*a) }
|
|
102
|
+
end
|
|
103
|
+
@node.subscribe(:block) do |block, depth|
|
|
104
|
+
block = @node.store.get_block_by_depth(depth - conf.to_i + 1)
|
|
105
|
+
next unless block
|
|
106
|
+
block.tx.each {|tx| @node.notifiers["tx_#{conf}".to_sym].push([tx, conf.to_i]) }
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Handle +monitor output+ command.
|
|
111
|
+
# Receive tx hash, recipient address and value for each output.
|
|
112
|
+
# This allows easy scanning for new payments without parsing the
|
|
113
|
+
# tx format and running scripts.
|
|
114
|
+
# See #handle_monitor_tx for confirmation behavior.
|
|
115
|
+
def handle_monitor_output conf = 0
|
|
116
|
+
return unless (conf = conf.to_i) > 0
|
|
117
|
+
@node.subscribe(:block) do |block, depth|
|
|
118
|
+
block = @node.store.get_block_by_depth(depth - conf + 1)
|
|
119
|
+
next unless block
|
|
120
|
+
block.tx.each do |tx|
|
|
121
|
+
tx.out.each do |out|
|
|
122
|
+
addr = Bitcoin::Script.new(out.pk_script).get_address
|
|
123
|
+
res = [tx.hash, addr, out.value, conf]
|
|
124
|
+
@node.push_notification("output_#{conf}".to_sym, res)
|
|
60
125
|
end
|
|
61
126
|
end
|
|
62
127
|
end
|
|
63
|
-
nil
|
|
64
128
|
end
|
|
65
129
|
|
|
66
|
-
#
|
|
130
|
+
# Handle +monitor connection+ command; send current connections
|
|
131
|
+
# after client is subscribed to :connection channel.
|
|
132
|
+
def handle_monitor_connection
|
|
133
|
+
@node.connections.select {|c| c.connected?}.each do |conn|
|
|
134
|
+
respond("monitor", [:connection, [:connected, conn.info]])
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Get various statistics.
|
|
67
139
|
# bitcoin_node info
|
|
68
140
|
def handle_info
|
|
69
|
-
blocks = @node.connections.map(&:version).compact.map(&:
|
|
70
|
-
{
|
|
71
|
-
|
|
141
|
+
blocks = @node.connections.map(&:version).compact.map(&:last_block) rescue nil
|
|
142
|
+
established = @node.connections.select {|c| c.state == :connected }
|
|
143
|
+
info = {
|
|
144
|
+
:blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' )})#{@node.store.in_sync? ? ' sync' : ''}",
|
|
72
145
|
:addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
|
|
73
|
-
:connections => "#{
|
|
146
|
+
:connections => "#{established.size} established (#{established.select(&:outgoing?).size} out, #{established.select(&:incoming?).size} in), #{@node.connections.size - established.size} connecting",
|
|
74
147
|
:queue => @node.queue.size,
|
|
75
148
|
:inv_queue => @node.inv_queue.size,
|
|
76
149
|
:inv_cache => @node.inv_cache.size,
|
|
77
150
|
:network => @node.config[:network],
|
|
78
151
|
:storage => @node.config[:storage],
|
|
79
|
-
:version => Bitcoin
|
|
152
|
+
:version => Bitcoin.network[:protocol_version],
|
|
153
|
+
:external_ip => @node.external_ip,
|
|
80
154
|
:uptime => format_uptime(@node.uptime),
|
|
81
155
|
}
|
|
156
|
+
Bitcoin.namecoin? ? {:names => @node.store.db[:names].count}.merge(info) : info
|
|
82
157
|
end
|
|
83
158
|
|
|
84
|
-
#
|
|
159
|
+
# Get the currently active configuration.
|
|
85
160
|
# bitcoin_node config
|
|
86
161
|
def handle_config
|
|
87
162
|
@node.config
|
|
88
163
|
end
|
|
89
164
|
|
|
90
|
-
#
|
|
165
|
+
# Get currently connected peers.
|
|
91
166
|
# bitcoin_node connections
|
|
92
167
|
def handle_connections
|
|
93
168
|
@node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
|
|
94
|
-
"#{c.host.rjust(15)}:#{c.port} [state: #{c.state}, " +
|
|
169
|
+
"#{c.host.rjust(15)}:#{c.port} [#{c.direction}, state: #{c.state}, " +
|
|
95
170
|
"version: #{c.version.version rescue '?'}, " +
|
|
96
171
|
"block: #{c.version.block rescue '?'}, " +
|
|
97
172
|
"uptime: #{format_uptime(c.uptime) rescue 0}, " +
|
|
98
173
|
"client: #{c.version.user_agent rescue '?'}]" }
|
|
99
174
|
end
|
|
100
175
|
|
|
101
|
-
#
|
|
176
|
+
# Connect to given peer(s).
|
|
102
177
|
# bitcoin_node connect <ip>:<port>[,<ip>:<port>]
|
|
103
178
|
def handle_connect *args
|
|
104
179
|
args.each {|a| @node.connect_peer(*a.split(':')) }
|
|
105
180
|
{:state => "Connecting..."}
|
|
106
181
|
end
|
|
107
182
|
|
|
108
|
-
#
|
|
183
|
+
# Disconnect given peer(s).
|
|
109
184
|
# bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]
|
|
110
185
|
def handle_disconnect *args
|
|
111
186
|
args.each do |c|
|
|
@@ -116,21 +191,21 @@ class Bitcoin::Network::CommandHandler < EM::Connection
|
|
|
116
191
|
{:state => "Disconnected"}
|
|
117
192
|
end
|
|
118
193
|
|
|
119
|
-
#
|
|
194
|
+
# Trigger the node to ask its peers for new blocks.
|
|
120
195
|
# bitcoin_node getblocks
|
|
121
196
|
def handle_getblocks
|
|
122
197
|
@node.connections.sample.send_getblocks
|
|
123
198
|
{:state => "Sending getblocks..."}
|
|
124
199
|
end
|
|
125
200
|
|
|
126
|
-
#
|
|
201
|
+
# Trigger the node to ask its for new peer addresses.
|
|
127
202
|
# bitcoin_node getaddr
|
|
128
203
|
def handle_getaddr
|
|
129
204
|
@node.connections.sample.send_getaddr
|
|
130
205
|
{:state => "Sending getaddr..."}
|
|
131
206
|
end
|
|
132
207
|
|
|
133
|
-
#
|
|
208
|
+
# Get known peer addresses (used by bin/bitcoin_dns_seed).
|
|
134
209
|
# bitcoin_node addrs [count]
|
|
135
210
|
def handle_addrs count = 32
|
|
136
211
|
@node.addrs.weighted_sample(count.to_i) do |addr|
|
|
@@ -140,28 +215,139 @@ class Bitcoin::Network::CommandHandler < EM::Connection
|
|
|
140
215
|
end.compact
|
|
141
216
|
end
|
|
142
217
|
|
|
143
|
-
#
|
|
144
|
-
# bitcoin_node
|
|
145
|
-
def
|
|
146
|
-
|
|
147
|
-
|
|
218
|
+
# Trigger a rescan operation when used with a UtxoStore.
|
|
219
|
+
# bitcoin_node rescan
|
|
220
|
+
def handle_rescan
|
|
221
|
+
EM.defer { @node.store.rescan }
|
|
222
|
+
{:state => "Rescanning ..."}
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Get Time Since Last Block.
|
|
226
|
+
# bitcoin_node tslb
|
|
227
|
+
def handle_tslb
|
|
228
|
+
{ tslb: (Time.now - @node.last_block_time).to_i }
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Create a transaction, collecting outputs from given +keys+, spending to +recipients+
|
|
232
|
+
# with an optional +fee+.
|
|
233
|
+
# Keys is an array that can contain either privkeys, pubkeys or addresses.
|
|
234
|
+
# When a privkey is given, the corresponding inputs are signed. If not, the
|
|
235
|
+
# signature_hash is computed and passed along with the response.
|
|
236
|
+
# After creating an unsigned transaction, one just needs to sign the sig_hashes
|
|
237
|
+
# and send everything to #assemble_tx, to receive the complete transaction that
|
|
238
|
+
# can be relayed to the network.
|
|
239
|
+
def handle_create_tx keys, recipients, fee = 0
|
|
240
|
+
keystore = Bitcoin::Wallet::SimpleKeyStore.new(file: StringIO.new("[]"))
|
|
241
|
+
keys.each do |k|
|
|
242
|
+
begin
|
|
243
|
+
key = Bitcoin::Key.from_base58(k)
|
|
244
|
+
key = { addr: key.addr, key: key }
|
|
245
|
+
rescue
|
|
246
|
+
if Bitcoin.valid_address?(k)
|
|
247
|
+
key = { addr: k }
|
|
248
|
+
else
|
|
249
|
+
begin
|
|
250
|
+
key = Bitcoin::Key.new(nil, k)
|
|
251
|
+
key = { addr: key.addr, key: key }
|
|
252
|
+
rescue
|
|
253
|
+
return { error: "Input not valid address, pub- or privkey: #{k}" }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
keystore.add_key(key)
|
|
258
|
+
end
|
|
259
|
+
wallet = Bitcoin::Wallet::Wallet.new(@node.store, keystore)
|
|
260
|
+
tx = wallet.new_tx(recipients.map {|r| [:address, r[0], r[1]]}, fee)
|
|
261
|
+
return { error: "Error creating tx." } unless tx
|
|
262
|
+
[ tx.to_payload.hth, tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } ]
|
|
148
263
|
rescue
|
|
149
|
-
{:
|
|
264
|
+
{ error: "Error creating tx: #{$!.message}" }
|
|
150
265
|
end
|
|
151
266
|
|
|
152
|
-
#
|
|
267
|
+
# Assemble an unsigned transaction from the +tx_hex+ and +sig_pubkeys+.
|
|
268
|
+
# The +tx_hex+ is the regular transaction structure, with empty input scripts
|
|
269
|
+
# (as returned by #create_tx when called without privkeys).
|
|
270
|
+
# +sig_pubkeys+ is an array of [signature, pubkey] pairs used to build the
|
|
271
|
+
# input scripts.
|
|
272
|
+
def handle_assemble_tx tx_hex, sig_pubs
|
|
273
|
+
tx = Bitcoin::P::Tx.new(tx_hex.htb)
|
|
274
|
+
sig_pubs.each.with_index do |sig_pub, idx|
|
|
275
|
+
sig, pub = *sig_pub.map(&:htb)
|
|
276
|
+
script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, pub)
|
|
277
|
+
tx.in[idx].script_sig_length = script_sig.bytesize
|
|
278
|
+
tx.in[idx].script_sig = script_sig
|
|
279
|
+
end
|
|
280
|
+
tx = Bitcoin::P::Tx.new(tx.to_payload)
|
|
281
|
+
tx.validator(@node.store).validate(raise_errors: true)
|
|
282
|
+
tx.to_payload.hth
|
|
283
|
+
rescue
|
|
284
|
+
{ error: "Error assembling tx: #{$!.message}" }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Relay given transaction (in hex).
|
|
288
|
+
# bitcoin_node relay_tx <tx in hex>
|
|
289
|
+
def handle_relay_tx hex, send = 3, wait = 3
|
|
290
|
+
begin
|
|
291
|
+
tx = Bitcoin::P::Tx.new(hex.htb)
|
|
292
|
+
rescue
|
|
293
|
+
return respond("relay_tx", { error: "Error decoding transaction." })
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
validator = tx.validator(@node.store)
|
|
297
|
+
unless validator.validate(rules: [:syntax])
|
|
298
|
+
return respond("relay_tx", { error: "Transaction syntax invalid.",
|
|
299
|
+
details: validator.error })
|
|
300
|
+
end
|
|
301
|
+
unless validator.validate(rules: [:context])
|
|
302
|
+
return respond("relay_tx", { error: "Transaction context invalid.",
|
|
303
|
+
details: validator.error })
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
#@node.store.store_tx(tx)
|
|
307
|
+
@node.relay_tx[tx.hash] = tx
|
|
308
|
+
@node.relay_propagation[tx.hash] = 0
|
|
309
|
+
@node.connections.select(&:connected?).sample(send).each {|c| c.send_inv(:tx, tx.hash) }
|
|
310
|
+
|
|
311
|
+
EM.add_timer(wait) do
|
|
312
|
+
received = @node.relay_propagation[tx.hash]
|
|
313
|
+
total = @node.connections.select(&:connected?).size - send
|
|
314
|
+
percent = 100.0 / total * received
|
|
315
|
+
respond("relay_tx", { success: true, hash: tx.hash, propagation: {
|
|
316
|
+
received: received, sent: 1, percent: percent } })
|
|
317
|
+
end
|
|
318
|
+
rescue
|
|
319
|
+
respond("relay_tx", { error: $!.message, backtrace: $@ })
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Stop the bitcoin node.
|
|
153
323
|
# bitcoin_node stop
|
|
154
324
|
def handle_stop
|
|
155
325
|
Thread.start { sleep 0.1; @node.stop }
|
|
156
326
|
{:state => "Stopping..."}
|
|
157
327
|
end
|
|
158
328
|
|
|
159
|
-
#
|
|
329
|
+
# List all available commands.
|
|
160
330
|
# bitcoin_node help
|
|
161
331
|
def handle_help
|
|
162
332
|
self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
|
|
163
333
|
end
|
|
164
334
|
|
|
335
|
+
# Validate and store given block (in hex) as if it was received by a peer.
|
|
336
|
+
# bitcoin_node store_block <block in hex>
|
|
337
|
+
def handle_store_block hex
|
|
338
|
+
block = Bitcoin::P::Block.new(hex.htb)
|
|
339
|
+
@node.queue << [:block, block]
|
|
340
|
+
{ queued: [ :block, block.hash ] }
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Store given transaction (in hex) as if it was received by a peer.
|
|
344
|
+
# bitcoin_node store_tx <tx in hex>
|
|
345
|
+
def handle_store_tx hex
|
|
346
|
+
tx = Bitcoin::P::Tx.new(hex.htb)
|
|
347
|
+
@node.queue << [:tx, tx]
|
|
348
|
+
{ queued: [ :tx, tx.hash ] }
|
|
349
|
+
end
|
|
350
|
+
|
|
165
351
|
# format node uptime
|
|
166
352
|
def format_uptime t
|
|
167
353
|
mm, ss = t.divmod(60) #=> [4515, 21]
|
|
@@ -1,18 +1,24 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
require 'eventmachine'
|
|
2
4
|
|
|
3
5
|
module Bitcoin::Network
|
|
4
6
|
|
|
5
7
|
# Node network connection to a peer. Handles all the communication with a specific peer.
|
|
6
|
-
# TODO: incoming/outgoing?
|
|
7
8
|
class ConnectionHandler < EM::Connection
|
|
8
9
|
|
|
10
|
+
LATENCY_MAX = (5*60*1000) # 5min in ms
|
|
11
|
+
|
|
9
12
|
include Bitcoin
|
|
10
13
|
include Bitcoin::Storage
|
|
11
14
|
|
|
12
|
-
attr_reader :host, :port, :
|
|
15
|
+
attr_reader :host, :port, :version, :direction
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
16
22
|
|
|
17
23
|
def log
|
|
18
24
|
@log ||= Logger::LogWrapper.new("#@host:#@port", @node.log)
|
|
@@ -24,12 +30,17 @@ module Bitcoin::Network
|
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
# create connection to +host+:+port+ for given +node+
|
|
27
|
-
def initialize node, host, port
|
|
28
|
-
@node, @host, @port = node, host, port
|
|
33
|
+
def initialize node, host, port, direction
|
|
34
|
+
@node, @host, @port, @direction = node, host, port, direction
|
|
29
35
|
@parser = Bitcoin::Protocol::Parser.new(self)
|
|
30
36
|
@state = :new
|
|
31
37
|
@version = nil
|
|
32
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
|
|
33
44
|
rescue Exception
|
|
34
45
|
log.fatal { "Error in #initialize" }
|
|
35
46
|
p $!; puts $@; exit
|
|
@@ -37,36 +48,73 @@ module Bitcoin::Network
|
|
|
37
48
|
|
|
38
49
|
# check if connection is wanted, begin handshake if it is, disconnect if not
|
|
39
50
|
def post_init
|
|
40
|
-
if
|
|
41
|
-
|
|
51
|
+
if incoming?
|
|
52
|
+
begin_handshake
|
|
42
53
|
end
|
|
43
|
-
log.info { "Connected to #{@host}:#{@port}" }
|
|
44
|
-
@state = :established
|
|
45
|
-
@node.connections << self
|
|
46
|
-
on_handshake_begin
|
|
47
54
|
rescue Exception
|
|
48
55
|
log.fatal { "Error in #post_init" }
|
|
49
|
-
p $!; puts
|
|
56
|
+
p $!; puts *$@
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# only called for outgoing connection
|
|
60
|
+
def connection_completed
|
|
61
|
+
begin_handshake
|
|
62
|
+
rescue Exception
|
|
63
|
+
log.fatal { "Error in #connection_completed" }
|
|
64
|
+
p $!; puts *$@
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
# receive data from peer and invoke Protocol::Parser
|
|
53
68
|
def receive_data data
|
|
54
69
|
#log.debug { "Receiving data (#{data.size} bytes)" }
|
|
55
|
-
@parser.parse(data)
|
|
70
|
+
@lock.synchronize { @parser.parse(data) }
|
|
71
|
+
rescue
|
|
72
|
+
log.warn { "Error handling data: #{data.hth}" }
|
|
73
|
+
p $!; puts *$@
|
|
56
74
|
end
|
|
57
75
|
|
|
58
76
|
# connection closed; notify listeners and cleanup connection from node
|
|
59
77
|
def unbind
|
|
60
|
-
log.info { "Disconnected
|
|
61
|
-
@node.
|
|
78
|
+
log.info { "Disconnected" }
|
|
79
|
+
@node.push_notification(:connection, [:disconnected, [@host, @port]])
|
|
62
80
|
@state = :disconnected
|
|
63
81
|
@node.connections.delete(self)
|
|
64
82
|
end
|
|
65
83
|
|
|
84
|
+
# begin handshake
|
|
85
|
+
# TODO: disconnect if we don't complete within a reasonable time
|
|
86
|
+
def begin_handshake
|
|
87
|
+
if incoming? && !@node.accept_connections?
|
|
88
|
+
return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
|
|
89
|
+
end
|
|
90
|
+
log.info { "Established #{@direction} connection" }
|
|
91
|
+
@node.connections << self
|
|
92
|
+
@state = :handshake
|
|
93
|
+
# incoming connections wait to receive a version
|
|
94
|
+
send_version if outgoing?
|
|
95
|
+
rescue Exception
|
|
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, [:connected, info])
|
|
107
|
+
@node.addrs << addr
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
66
111
|
# received +inv_tx+ message for given +hash+.
|
|
67
112
|
# add to inv_queue, unlesss maximum is reached
|
|
68
113
|
def on_inv_transaction(hash)
|
|
69
|
-
log.debug { ">> inv transaction: #{hth
|
|
114
|
+
log.debug { ">> inv transaction: #{hash.hth}" }
|
|
115
|
+
if @node.relay_propagation.keys.include?(hash.hth)
|
|
116
|
+
@node.relay_propagation[hash.hth] += 1
|
|
117
|
+
end
|
|
70
118
|
return if @node.inv_queue.size >= @node.config[:max][:inv]
|
|
71
119
|
@node.queue_inv([:tx, hash, self])
|
|
72
120
|
end
|
|
@@ -74,7 +122,7 @@ module Bitcoin::Network
|
|
|
74
122
|
# received +inv_block+ message for given +hash+.
|
|
75
123
|
# add to inv_queue, unless maximum is reached
|
|
76
124
|
def on_inv_block(hash)
|
|
77
|
-
log.debug { ">> inv block: #{hth
|
|
125
|
+
log.debug { ">> inv block: #{hash.hth}" }
|
|
78
126
|
return if @node.inv_queue.size >= @node.config[:max][:inv]
|
|
79
127
|
@node.queue_inv([:block, hash, self])
|
|
80
128
|
end
|
|
@@ -82,8 +130,9 @@ module Bitcoin::Network
|
|
|
82
130
|
# received +get_tx+ message for given +hash+.
|
|
83
131
|
# send specified tx if we have it
|
|
84
132
|
def on_get_transaction(hash)
|
|
85
|
-
log.debug { ">> get transaction: #{hash.
|
|
86
|
-
tx = @node.store.get_tx(hash.
|
|
133
|
+
log.debug { ">> get transaction: #{hash.hth}" }
|
|
134
|
+
tx = @node.store.get_tx(hash.hth)
|
|
135
|
+
tx ||= @node.relay_tx[hash.hth]
|
|
87
136
|
return unless tx
|
|
88
137
|
pkt = Bitcoin::Protocol.pkt("tx", tx.to_payload)
|
|
89
138
|
log.debug { "<< tx: #{tx.hash}" }
|
|
@@ -92,16 +141,13 @@ module Bitcoin::Network
|
|
|
92
141
|
|
|
93
142
|
# received +get_block+ message for given +hash+.
|
|
94
143
|
# send specified block if we have it
|
|
95
|
-
# TODO
|
|
96
144
|
def on_get_block(hash)
|
|
97
|
-
log.debug { ">> get block: #{hth
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
pkt
|
|
103
|
-
log.debug { "<< inv #{type}: #{obj.hash}" }
|
|
104
|
-
send_data(pkt)
|
|
145
|
+
log.debug { ">> get block: #{hash.hth}" }
|
|
146
|
+
blk = @node.store.get_block(hash.hth)
|
|
147
|
+
return unless blk
|
|
148
|
+
pkt = Bitcoin::Protocol.pkt("block", blk.to_payload)
|
|
149
|
+
log.debug { "<< block: #{blk.hash}" }
|
|
150
|
+
send_data pkt
|
|
105
151
|
end
|
|
106
152
|
|
|
107
153
|
# received +addr+ message for given +addr+.
|
|
@@ -109,7 +155,7 @@ module Bitcoin::Network
|
|
|
109
155
|
def on_addr(addr)
|
|
110
156
|
log.debug { ">> addr: #{addr.ip}:#{addr.port} alive: #{addr.alive?}, service: #{addr.service}" }
|
|
111
157
|
@node.addrs << addr
|
|
112
|
-
@node.
|
|
158
|
+
@node.push_notification(:addr, addr)
|
|
113
159
|
end
|
|
114
160
|
|
|
115
161
|
# received +tx+ message for given +tx+.
|
|
@@ -136,18 +182,23 @@ module Bitcoin::Network
|
|
|
136
182
|
# received +version+ message for given +version+.
|
|
137
183
|
# send +verack+ message and complete handshake
|
|
138
184
|
def on_version(version)
|
|
139
|
-
log.
|
|
185
|
+
log.debug { ">> version: #{version.version}" }
|
|
186
|
+
@node.external_ips << version.to.split(":")[0]
|
|
140
187
|
@version = version
|
|
141
|
-
log.
|
|
188
|
+
log.debug { "<< verack" }
|
|
142
189
|
send_data( Protocol.verack_pkt )
|
|
143
|
-
|
|
190
|
+
|
|
191
|
+
# sometimes other nodes don't bother to send a verack back,
|
|
192
|
+
# but we can consider the handshake complete once we sent ours.
|
|
193
|
+
# apparently it can happen on incoming and outgoing connections alike
|
|
194
|
+
complete_handshake
|
|
144
195
|
end
|
|
145
196
|
|
|
146
197
|
# received +verack+ message.
|
|
147
198
|
# complete handshake if it isn't completed already
|
|
148
199
|
def on_verack
|
|
149
|
-
log.
|
|
150
|
-
|
|
200
|
+
log.debug { ">> verack" }
|
|
201
|
+
complete_handshake if outgoing?
|
|
151
202
|
end
|
|
152
203
|
|
|
153
204
|
# received +alert+ message for given +alert+.
|
|
@@ -156,25 +207,80 @@ module Bitcoin::Network
|
|
|
156
207
|
log.warn { ">> alert: #{alert.inspect}" }
|
|
157
208
|
end
|
|
158
209
|
|
|
210
|
+
# received +getblocks+ message.
|
|
211
|
+
# TODO: locator fallback
|
|
212
|
+
def on_getblocks(version, hashes, stop_hash)
|
|
213
|
+
# remember the last few received getblocks messages and ignore duplicate ones
|
|
214
|
+
# fixes unexplained issue where remote node is bombarding us with the same getblocks
|
|
215
|
+
# message over and over (probably related to missing locator fallback handling)
|
|
216
|
+
return if @last_getblocks && @last_getblocks.include?([version, hashes, stop_hash])
|
|
217
|
+
@last_getblocks << [version, hashes, stop_hash]
|
|
218
|
+
@last_getblocks.shift if @last_getblocks.size > 3
|
|
219
|
+
|
|
220
|
+
blk = @node.store.db[:blk][hash: hashes[0].htb.blob]
|
|
221
|
+
depth = blk[:depth] if blk
|
|
222
|
+
log.info { ">> getblocks #{hashes[0]} (#{depth || 'unknown'})" }
|
|
223
|
+
|
|
224
|
+
return unless depth && depth <= @node.store.get_depth
|
|
225
|
+
range = (depth+1..depth+500)
|
|
226
|
+
blocks = @node.store.db[:blk].where(chain: 0, depth: range).select(:hash).all +
|
|
227
|
+
[@node.store.db[:blk].select(:hash)[chain: 0, depth: depth+502]]
|
|
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.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250]
|
|
235
|
+
log.debug { "<< addr (#{addrs.size})" }
|
|
236
|
+
send_data P::Addr.pkt(*addrs)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# begin handshake; send +version+ message
|
|
240
|
+
def send_version
|
|
241
|
+
from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
|
|
242
|
+
version = Bitcoin::Protocol::Version.new({
|
|
243
|
+
:version => 70001,
|
|
244
|
+
:last_block => @node.store.get_depth,
|
|
245
|
+
:from => from,
|
|
246
|
+
:to => @host,
|
|
247
|
+
:user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
|
|
248
|
+
#:user_agent => "/Satoshi:0.8.3/",
|
|
249
|
+
})
|
|
250
|
+
send_data(version.to_pkt)
|
|
251
|
+
log.debug { "<< version: #{Bitcoin.network[:protocol_version]}" }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# send +inv+ message with given +type+ for given +obj+
|
|
255
|
+
def send_inv type, *hashes
|
|
256
|
+
hashes.each_slice(251) do |slice|
|
|
257
|
+
pkt = Protocol.inv_pkt(type, slice.map(&:htb))
|
|
258
|
+
log.debug { "<< inv #{type}: #{slice[0][0..16]}" + (slice.size > 1 ? "..#{slice[-1][0..16]}" : "") }
|
|
259
|
+
send_data(pkt)
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
159
263
|
# send +getdata tx+ message for given tx +hash+
|
|
160
264
|
def send_getdata_tx(hash)
|
|
161
265
|
pkt = Protocol.getdata_pkt(:tx, [hash])
|
|
162
|
-
log.debug { "<< getdata tx: #{hth
|
|
266
|
+
log.debug { "<< getdata tx: #{hash.hth}" }
|
|
163
267
|
send_data(pkt)
|
|
164
268
|
end
|
|
165
269
|
|
|
166
270
|
# send +getdata block+ message for given block +hash+
|
|
167
271
|
def send_getdata_block(hash)
|
|
168
272
|
pkt = Protocol.getdata_pkt(:block, [hash])
|
|
169
|
-
log.debug { "<< getdata block: #{hth
|
|
273
|
+
log.debug { "<< getdata block: #{hash.hth}" }
|
|
170
274
|
send_data(pkt)
|
|
171
275
|
end
|
|
172
276
|
|
|
173
277
|
# send +getblocks+ message
|
|
174
278
|
def send_getblocks locator = @node.store.get_locator
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
279
|
+
if @node.store.get_depth == -1
|
|
280
|
+
EM.add_timer(3) { send_getblocks }
|
|
281
|
+
return get_genesis_block
|
|
282
|
+
end
|
|
283
|
+
pkt = Protocol.getblocks_pkt(@version.version, locator)
|
|
178
284
|
log.info { "<< getblocks: #{locator.first}" }
|
|
179
285
|
send_data(pkt)
|
|
180
286
|
end
|
|
@@ -183,7 +289,7 @@ module Bitcoin::Network
|
|
|
183
289
|
def send_getheaders locator = @node.store.get_locator
|
|
184
290
|
return get_genesis_block if @node.store.get_depth == -1
|
|
185
291
|
pkt = Protocol.pkt("getheaders", [Bitcoin::network[:magic_head],
|
|
186
|
-
locator.size.chr, *locator.map{|l|
|
|
292
|
+
locator.size.chr, *locator.map{|l| l.htb_reverse}, "\x00"*32].join)
|
|
187
293
|
log.debug { "<< getheaders: #{locator.first}" }
|
|
188
294
|
send_data(pkt)
|
|
189
295
|
end
|
|
@@ -197,30 +303,27 @@ module Bitcoin::Network
|
|
|
197
303
|
# send +ping+ message
|
|
198
304
|
# TODO: wait for pong and disconnect if it doesn't arrive (and version is new enough)
|
|
199
305
|
def send_ping
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
306
|
+
if @version.version > Bitcoin::Protocol::BIP0031_VERSION
|
|
307
|
+
@latency_ms = LATENCY_MAX
|
|
308
|
+
@ping_nonce = rand(0xffffffff)
|
|
309
|
+
@ping_time = Time.now
|
|
310
|
+
log.debug { "<< ping (#{@ping_nonce})" }
|
|
311
|
+
send_data(Protocol.ping_pkt(@ping_nonce))
|
|
312
|
+
else
|
|
313
|
+
# set latency to 5 seconds, terrible but this version should be obsolete now
|
|
314
|
+
@latency_ms = (5*1000)
|
|
315
|
+
log.debug { "<< ping" }
|
|
316
|
+
send_data(Protocol.ping_pkt)
|
|
317
|
+
end
|
|
203
318
|
end
|
|
204
319
|
|
|
205
320
|
# ask for the genesis block
|
|
206
321
|
def get_genesis_block
|
|
207
322
|
log.info { "Asking for genesis block" }
|
|
208
|
-
pkt = Protocol.getdata_pkt(:block, [
|
|
323
|
+
pkt = Protocol.getdata_pkt(:block, [Bitcoin::network[:genesis_hash].htb])
|
|
209
324
|
send_data(pkt)
|
|
210
325
|
end
|
|
211
326
|
|
|
212
|
-
# complete handshake; set state, started time, notify listeners and add address to Node
|
|
213
|
-
def on_handshake_complete
|
|
214
|
-
return unless handshake?
|
|
215
|
-
log.debug { "handshake complete" }
|
|
216
|
-
@state = :connected
|
|
217
|
-
@started = Time.now
|
|
218
|
-
@node.notifiers[:connection].push([:connected, info])
|
|
219
|
-
@node.addrs << addr
|
|
220
|
-
# send_getaddr
|
|
221
|
-
# EM.add_periodic_timer(15) { send_ping }
|
|
222
|
-
end
|
|
223
|
-
|
|
224
327
|
# received +ping+ message with given +nonce+.
|
|
225
328
|
# send +pong+ message back, if +nonce+ is set.
|
|
226
329
|
# network versions <=60000 don't set the nonce and don't expect a pong.
|
|
@@ -230,28 +333,33 @@ module Bitcoin::Network
|
|
|
230
333
|
end
|
|
231
334
|
|
|
232
335
|
# received +pong+ message with given +nonce+.
|
|
233
|
-
# TODO: see #send_ping
|
|
234
336
|
def on_pong nonce
|
|
235
|
-
|
|
337
|
+
if @ping_nonce == nonce
|
|
338
|
+
@latency_ms = (Time.now - @ping_time) * 1000.0
|
|
339
|
+
end
|
|
340
|
+
log.debug { ">> pong (#{nonce}), latency: #{@latency_ms.to_i}ms" }
|
|
236
341
|
end
|
|
237
342
|
|
|
238
343
|
# begin handshake; send +version+ message
|
|
239
344
|
def on_handshake_begin
|
|
240
345
|
@state = :handshake
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
346
|
+
from = "#{@node.external_ip}:#{@node.config[:listen][1]}"
|
|
347
|
+
version = Bitcoin::Protocol::Version.new({
|
|
348
|
+
:version => 70001,
|
|
349
|
+
:last_block => @node.store.get_depth,
|
|
350
|
+
:from => from,
|
|
351
|
+
:to => @host,
|
|
352
|
+
:user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
|
|
353
|
+
#:user_agent => "/Satoshi:0.8.1/",
|
|
354
|
+
})
|
|
355
|
+
send_data(version.to_pkt)
|
|
356
|
+
log.debug { "<< version (#{Bitcoin.network[:protocol_version]})" }
|
|
249
357
|
end
|
|
250
358
|
|
|
251
359
|
# get Addr object for this connection
|
|
252
360
|
def addr
|
|
253
361
|
return @addr if @addr
|
|
254
|
-
@addr =
|
|
362
|
+
@addr = P::Addr.new
|
|
255
363
|
@addr.time, @addr.service, @addr.ip, @addr.port =
|
|
256
364
|
Time.now.tv_sec, @version.services, @host, @port
|
|
257
365
|
@addr
|
|
@@ -265,10 +373,14 @@ module Bitcoin::Network
|
|
|
265
373
|
def info
|
|
266
374
|
{
|
|
267
375
|
:host => @host, :port => @port, :state => @state,
|
|
268
|
-
:version => @version.version, :block => @version.
|
|
269
|
-
:user_agent => @version.user_agent
|
|
376
|
+
:version => (@version.version rescue 0), :block => @version.last_block,
|
|
377
|
+
:started => @started.to_i, :user_agent => @version.user_agent
|
|
270
378
|
}
|
|
271
379
|
end
|
|
380
|
+
|
|
381
|
+
def incoming?; @direction == :in; end
|
|
382
|
+
def outgoing?; @direction == :out; end
|
|
383
|
+
|
|
272
384
|
end
|
|
273
385
|
|
|
274
386
|
end
|