bitcoin-ruby 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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,570 +0,0 @@
|
|
1
|
-
# encoding: ascii-8bit
|
2
|
-
|
3
|
-
require 'json'
|
4
|
-
require 'monitor'
|
5
|
-
|
6
|
-
# Started by the Node, accepts connections from CommandClient and answers requests or
|
7
|
-
# registers for events and notifies the clients when they happen.
|
8
|
-
class Bitcoin::Network::CommandHandler < EM::Connection
|
9
|
-
|
10
|
-
# create new CommandHandler
|
11
|
-
def initialize node
|
12
|
-
@node = node
|
13
|
-
@node.command_connections << self
|
14
|
-
@buf = BufferedTokenizer.new("\x00")
|
15
|
-
@lock = Monitor.new
|
16
|
-
@monitors = []
|
17
|
-
end
|
18
|
-
|
19
|
-
# wrap logger and append prefix
|
20
|
-
def log
|
21
|
-
@log ||= Bitcoin::Logger::LogWrapper.new("command:", @node.log)
|
22
|
-
end
|
23
|
-
|
24
|
-
# respond to a command; send serialized response to the client
|
25
|
-
def respond(request, data)
|
26
|
-
return unless data
|
27
|
-
request[:result] = data
|
28
|
-
request.delete(:params)
|
29
|
-
@lock.synchronize do
|
30
|
-
send_data(request.to_json + "\x00")
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# receive request from the client
|
35
|
-
def receive_data data
|
36
|
-
@buf.extract(data).each do |packet|
|
37
|
-
begin
|
38
|
-
request = symbolize_keys(JSON::parse(packet))
|
39
|
-
log.debug { request }
|
40
|
-
case request[:method]
|
41
|
-
when "relay_tx"
|
42
|
-
return handle_relay_tx(request, request[:params])
|
43
|
-
when "monitor"
|
44
|
-
respond(request, handle_monitor(request, request[:params]))
|
45
|
-
else
|
46
|
-
if respond_to?("handle_#{request[:method]}")
|
47
|
-
if request[:params] && request[:params].any?
|
48
|
-
respond(request, send("handle_#{request[:method]}", request[:params]))
|
49
|
-
else
|
50
|
-
respond(request, send("handle_#{request[:method]}"))
|
51
|
-
end
|
52
|
-
else
|
53
|
-
respond(request, { error: "unknown command: #{request[:method]}. send 'help' for help." })
|
54
|
-
end
|
55
|
-
end
|
56
|
-
rescue
|
57
|
-
respond(request, { error: $!.message })
|
58
|
-
p $!; puts *$@
|
59
|
-
end
|
60
|
-
end
|
61
|
-
rescue
|
62
|
-
p $!; puts *$@
|
63
|
-
end
|
64
|
-
|
65
|
-
def handle_connected
|
66
|
-
"connected"
|
67
|
-
end
|
68
|
-
|
69
|
-
# Handle +monitor+ command; subscribe client to specified channels
|
70
|
-
# (+block+, +tx+, +output+, +connection+).
|
71
|
-
# Parameters can be appended to the channel name, e.g. the number of confirmations
|
72
|
-
# +tx+ or +output+ should have. Parameters are appended to the command
|
73
|
-
# name after an underscore (_), e.g. subscribe to channel "tx_6" to
|
74
|
-
# receive only transactions with 6 confirmations.
|
75
|
-
# You can send the last block/tx/output you know about, and it will also send you
|
76
|
-
# all the objects you're missing.
|
77
|
-
#
|
78
|
-
# Receive new blocks:
|
79
|
-
# bitcoin_node monitor block
|
80
|
-
# Receive blocks since block 123, and new ones as they come in:
|
81
|
-
# bitcoin_node monitor block_123
|
82
|
-
# Receive new (unconfirmed) transactions:
|
83
|
-
# bitcoin_node monitor tx
|
84
|
-
# Receive transactions with 6 confirmations:
|
85
|
-
# bitcoin_node monitor tx_6
|
86
|
-
# Receive transactions since <txhash>, and new ones as they come in:
|
87
|
-
# bitcoin_node monitor tx_1_<txhash>
|
88
|
-
# Receive [txhash, idx, address, value] for each output:
|
89
|
-
# bitcoin_node monitor output
|
90
|
-
# Receive outputs since <txhash>:<idx>, and new ones as they come in:
|
91
|
-
# bitcoin_node monitor output_1_<txhash>:<idx>
|
92
|
-
# Receive peer connections/disconnections:
|
93
|
-
# bitcoin_node monitor connection"
|
94
|
-
# Combine multiple channels:
|
95
|
-
# bitcoin_node monitor "block tx tx_1 tx_6 connection"
|
96
|
-
#
|
97
|
-
# NOTE: When a new block is found, it might include transactions that we
|
98
|
-
# didn't previously receive as unconfirmed. To make sure you receive all
|
99
|
-
# transactions, also subscribe to the tx_1 channel.
|
100
|
-
def handle_monitor request, params
|
101
|
-
log.info { "Client subscribed to channel #{params[:channel].inspect}" }
|
102
|
-
{ id: send("handle_monitor_#{params[:channel]}", request, params) }
|
103
|
-
end
|
104
|
-
|
105
|
-
# Handle +unmonitor+ command; cancel given subscription.
|
106
|
-
# Parameter +id+ must be the subscription ID that was returned when calling +monitor+.
|
107
|
-
def handle_unmonitor request
|
108
|
-
id = request[:id]
|
109
|
-
raise "Monitor #{id} not found." unless @monitors[id]
|
110
|
-
@monitors[id][:channels].each {|name, id| @node.unsubscribe(name, id) }
|
111
|
-
{ id: id }
|
112
|
-
end
|
113
|
-
|
114
|
-
# Handle +monitor block+ command;
|
115
|
-
def handle_monitor_block request, params
|
116
|
-
monitor_id = @monitors.size
|
117
|
-
id = @node.subscribe(:block) {|blk, depth| respond_monitor_block(request, blk, depth) }
|
118
|
-
add_monitor(params, [[:block, id]])
|
119
|
-
respond_missed_blocks(request, monitor_id) if params[:last]
|
120
|
-
monitor_id
|
121
|
-
end
|
122
|
-
|
123
|
-
def respond_missed_blocks request, monitor_id
|
124
|
-
params = @monitors[monitor_id][:params]
|
125
|
-
blk = @node.store.get_block(params[:last])
|
126
|
-
respond_monitor_block(request, blk)
|
127
|
-
while blk = blk.get_next_block
|
128
|
-
respond_monitor_block(request, blk)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def respond_monitor_block request, block, depth = nil
|
133
|
-
depth ||= block.depth
|
134
|
-
respond(request, { hash: block.hash, hex: block.to_payload.hth, depth: depth })
|
135
|
-
end
|
136
|
-
|
137
|
-
# TODO: params (min reorg depth)
|
138
|
-
def handle_monitor_reorg request, params
|
139
|
-
id = @node.subscribe(:reorg) do |new_main, new_side|
|
140
|
-
respond(request, { new_main: new_main, new_side: new_side })
|
141
|
-
end
|
142
|
-
|
143
|
-
add_monitor(params, [[:reorg, id]])
|
144
|
-
end
|
145
|
-
|
146
|
-
# Handle +monitor tx+ command.
|
147
|
-
# When +conf+ is given, don't subscribe to the :tx channel for unconfirmed
|
148
|
-
# transactions. Instead, subscribe to the :block channel, and whenever a new
|
149
|
-
# block comes in, send all transactions that now have +conf+ confirmations.
|
150
|
-
def handle_monitor_tx request, params
|
151
|
-
monitor_id = @monitors.size
|
152
|
-
tx_id = @node.subscribe(:tx) {|tx, conf| respond_monitor_tx(request, monitor_id, tx, conf) }
|
153
|
-
|
154
|
-
conf = params[:conf].to_i
|
155
|
-
block_id = @node.subscribe(:block) do |block, depth|
|
156
|
-
next unless block = @node.store.get_block_by_depth(depth - conf + 1)
|
157
|
-
block.tx.each {|tx| respond_monitor_tx(request, monitor_id, tx, conf) }
|
158
|
-
end
|
159
|
-
|
160
|
-
add_monitor(params, [[:tx, tx_id], [:block, block_id]])
|
161
|
-
|
162
|
-
respond_missed_txs(request, params, monitor_id) if params[:last]
|
163
|
-
|
164
|
-
monitor_id
|
165
|
-
end
|
166
|
-
|
167
|
-
def respond_missed_txs request, params, monitor_id
|
168
|
-
return unless last_tx = @node.store.get_tx(params[:last])
|
169
|
-
notify = false; depth = @node.store.get_depth
|
170
|
-
(last_tx.get_block.depth..depth).each do |i|
|
171
|
-
blk = @node.store.get_block_by_depth(i)
|
172
|
-
blk.tx.each do |tx|
|
173
|
-
respond_monitor_tx(request, monitor_id, tx, (depth - blk.depth + 1)) if notify
|
174
|
-
notify = true if tx.hash == last_tx.hash
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
def respond_monitor_tx request, monitor_id, tx, conf = nil
|
180
|
-
conf ||= tx.confirmations
|
181
|
-
|
182
|
-
params = @monitors[monitor_id][:params]
|
183
|
-
|
184
|
-
# filter by addresses
|
185
|
-
if params[:addresses]
|
186
|
-
addrs = tx.out.map(&:parsed_script).map(&:get_address)
|
187
|
-
return unless (params[:addresses] & addrs).any?
|
188
|
-
end
|
189
|
-
|
190
|
-
respond(request, { hash: tx.hash, nhash: tx.nhash, hex: tx.to_payload.hth, conf: conf })
|
191
|
-
end
|
192
|
-
|
193
|
-
# Handle +monitor output+ command.
|
194
|
-
# Receive tx hash, recipient address and value for each output.
|
195
|
-
# This allows easy scanning for new payments without parsing the
|
196
|
-
# tx format and running scripts.
|
197
|
-
# See #handle_monitor_tx for confirmation behavior.
|
198
|
-
def handle_monitor_output request, params
|
199
|
-
monitor_id = @monitors.size
|
200
|
-
|
201
|
-
tx_id = @node.subscribe(:tx) do |tx, conf|
|
202
|
-
tx.out.each.with_index do |out, idx|
|
203
|
-
respond_monitor_output(request, monitor_id, tx, out, idx, conf)
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
if (conf = params[:conf].to_i) > 0
|
208
|
-
block_id = @node.subscribe(:block) do |block, depth|
|
209
|
-
block = @node.store.get_block_by_depth(depth - conf + 1)
|
210
|
-
next unless block
|
211
|
-
block.tx.each do |tx|
|
212
|
-
tx.out.each.with_index do |out, idx|
|
213
|
-
respond_monitor_output(request, monitor_id, tx, out, idx, conf)
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
217
|
-
end
|
218
|
-
|
219
|
-
add_monitor(params, [[:tx, tx_id], [:block, block_id]])
|
220
|
-
|
221
|
-
respond_missed_outputs(request, monitor_id) if params[:last]
|
222
|
-
|
223
|
-
monitor_id
|
224
|
-
end
|
225
|
-
|
226
|
-
def respond_missed_outputs request, monitor_id
|
227
|
-
params = @monitors[monitor_id][:params]
|
228
|
-
last_hash, last_idx = *params[:last].split(":"); last_idx = last_idx.to_i
|
229
|
-
return unless last_tx = @node.store.get_tx(last_hash)
|
230
|
-
return unless last_out = last_tx.out[last_idx]
|
231
|
-
notify = false
|
232
|
-
depth = @node.store.get_depth
|
233
|
-
(last_tx.get_block.depth..depth).each do |i|
|
234
|
-
blk = @node.store.get_block_by_depth(i)
|
235
|
-
blk.tx.each do |tx|
|
236
|
-
tx.out.each.with_index do |out, idx|
|
237
|
-
if notify
|
238
|
-
respond_monitor_output(request, monitor_id, tx, out, idx, (depth - blk.depth + 1))
|
239
|
-
else
|
240
|
-
notify = true if tx.hash == last_hash && idx == last_idx
|
241
|
-
end
|
242
|
-
end
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
def respond_monitor_output request, monitor_id, tx, out, idx, conf
|
248
|
-
addr = out.parsed_script.get_address
|
249
|
-
|
250
|
-
params = @monitors[monitor_id][:params]
|
251
|
-
|
252
|
-
# filter by addresses
|
253
|
-
return if params[:addresses] && !params[:addresses].include?(addr)
|
254
|
-
|
255
|
-
respond(request, { nhash: tx.nhash, hash: tx.hash, idx: idx,
|
256
|
-
address: addr, value: out.value, conf: conf })
|
257
|
-
end
|
258
|
-
|
259
|
-
# Handle +filter monitor output+ command; add given +address+ to the list of
|
260
|
-
# filtered addresses in the params of the given monitor.
|
261
|
-
def handle_filter_monitor_output request
|
262
|
-
@monitors[request[:id]][:params][:addresses] << request[:address]
|
263
|
-
{ id: request[:id] }
|
264
|
-
end
|
265
|
-
|
266
|
-
# Handle +monitor connection+ command; send current connections
|
267
|
-
# after client is subscribed to :connection channel.
|
268
|
-
def handle_monitor_connection request, params
|
269
|
-
id = @node.subscribe(:connection) {|data| respond(request, data) }
|
270
|
-
@node.connections.select {|c| c.connected?}.each do |conn|
|
271
|
-
respond(request, conn.info.merge(type: :connected))
|
272
|
-
end
|
273
|
-
add_monitor(params, [[:connection, id]])
|
274
|
-
end
|
275
|
-
|
276
|
-
# Get various statistics.
|
277
|
-
# bitcoin_node info
|
278
|
-
def handle_info
|
279
|
-
blocks = @node.connections.map(&:version).compact.map(&:last_block) rescue nil
|
280
|
-
established = @node.connections.select {|c| c.state == :connected }
|
281
|
-
info = {
|
282
|
-
blocks: {
|
283
|
-
depth: @node.store.get_depth,
|
284
|
-
peers: (blocks.inject{|a,b| a+=b; a } / blocks.size rescue '?' ),
|
285
|
-
sync: @node.store.in_sync?,
|
286
|
-
},
|
287
|
-
addrs: {
|
288
|
-
alive: @node.addrs.select{|a| a.alive?}.size,
|
289
|
-
total: @node.addrs.size,
|
290
|
-
},
|
291
|
-
connections: {
|
292
|
-
established: established.size,
|
293
|
-
outgoing: established.select(&:outgoing?).size,
|
294
|
-
incoming: established.select(&:incoming?).size,
|
295
|
-
connecting: @node.connections.size - established.size,
|
296
|
-
},
|
297
|
-
queue: @node.queue.size,
|
298
|
-
inv_queue: @node.inv_queue.size,
|
299
|
-
inv_cache: @node.inv_cache.size,
|
300
|
-
network: @node.config[:network],
|
301
|
-
storage: @node.config[:storage],
|
302
|
-
version: Bitcoin.network[:protocol_version],
|
303
|
-
external_ip: @node.external_ip,
|
304
|
-
uptime: @node.uptime,
|
305
|
-
}
|
306
|
-
Bitcoin.namecoin? ? {names: @node.store.db[:names].count}.merge(info) : info
|
307
|
-
end
|
308
|
-
|
309
|
-
def add_monitor params, channels
|
310
|
-
@monitors << { params: params, channels: channels }
|
311
|
-
@monitors.size - 1
|
312
|
-
end
|
313
|
-
|
314
|
-
|
315
|
-
# Get the currently active configuration.
|
316
|
-
# bitcoin_node config
|
317
|
-
def handle_config
|
318
|
-
@node.config
|
319
|
-
end
|
320
|
-
|
321
|
-
# Get currently connected peers.
|
322
|
-
# bitcoin_node connections
|
323
|
-
def handle_connections
|
324
|
-
@node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
|
325
|
-
{
|
326
|
-
type: c.direction, host: c.host, port: c.port, state: c.state,
|
327
|
-
uptime: c.uptime,
|
328
|
-
version: {
|
329
|
-
version: c.version.version,
|
330
|
-
services: c.version.services,
|
331
|
-
time: c.version.time,
|
332
|
-
nonce: c.version.nonce,
|
333
|
-
block: c.version.last_block,
|
334
|
-
client: (c.version.user_agent rescue '?'),
|
335
|
-
relay: c.version.relay,
|
336
|
-
}
|
337
|
-
}
|
338
|
-
}
|
339
|
-
end
|
340
|
-
|
341
|
-
# Connect to given peer(s).
|
342
|
-
# { method: "connect", params: {host: "localhost", port: 12345 }
|
343
|
-
def handle_connect params
|
344
|
-
@node.connect_peer(params[:host], params[:port])
|
345
|
-
{ state: :connecting }
|
346
|
-
end
|
347
|
-
|
348
|
-
# Disconnect given peer(s).
|
349
|
-
# { method: "disconnect", params: {host: "localhost", port: 12345 }
|
350
|
-
def handle_disconnect params
|
351
|
-
conn = @node.connections.find {|c| c.host == params[:host] && c.port == params[:port].to_i}
|
352
|
-
conn.close_connection if conn
|
353
|
-
{ state: :disconnected }
|
354
|
-
end
|
355
|
-
|
356
|
-
# Trigger the node to ask its peers for new blocks.
|
357
|
-
# { method: "getblocks", params: {} }
|
358
|
-
def handle_getblocks
|
359
|
-
conn = @node.connections.sample
|
360
|
-
if conn
|
361
|
-
conn.send_getblocks
|
362
|
-
{ state: :sent, peer: { host: conn.host, port: conn.port } }
|
363
|
-
else
|
364
|
-
raise "No peer connected"
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
# Trigger the node to ask its for new peer addresses.
|
369
|
-
# { method: "getaddr", params: {} }
|
370
|
-
def handle_getaddr
|
371
|
-
conn = @node.connections.sample
|
372
|
-
if conn
|
373
|
-
conn.send_getaddr
|
374
|
-
{ state: :sent, peer: { host: conn.host, port: conn.port } }
|
375
|
-
else
|
376
|
-
raise "No peer connected"
|
377
|
-
end
|
378
|
-
end
|
379
|
-
|
380
|
-
# Get known peer addresses (used by bin/bitcoin_dns_seed).
|
381
|
-
# { method: "getaddr", params: { count: 32 } }
|
382
|
-
def handle_addrs params = { count: 32 }
|
383
|
-
@node.addrs.weighted_sample(params[:count].to_i) do |addr|
|
384
|
-
Time.now.tv_sec + 7200 - addr.time
|
385
|
-
end.map do |addr|
|
386
|
-
[addr.ip, addr.port, Time.now.tv_sec - addr.time] rescue nil
|
387
|
-
end.compact
|
388
|
-
end
|
389
|
-
|
390
|
-
# Trigger a rescan operation when used with a UtxoStore.
|
391
|
-
# { method: "rescan" }
|
392
|
-
def handle_rescan
|
393
|
-
EM.defer {
|
394
|
-
begin
|
395
|
-
@node.store.rescan
|
396
|
-
rescue
|
397
|
-
puts "rescan: #{$!}"
|
398
|
-
end
|
399
|
-
}
|
400
|
-
{ state: :rescanning }
|
401
|
-
end
|
402
|
-
|
403
|
-
# Get Time Since Last Block.
|
404
|
-
# bitcoin_node tslb
|
405
|
-
def handle_tslb
|
406
|
-
{ tslb: (Time.now - @node.last_block_time).to_i }
|
407
|
-
end
|
408
|
-
|
409
|
-
# Create a transaction, collecting outputs from given +keys+, spending to +recipients+
|
410
|
-
# with an optional +fee+.
|
411
|
-
# Keys is an array that can contain either privkeys, pubkeys or addresses.
|
412
|
-
# When a privkey is given, the corresponding inputs are signed. If not, the
|
413
|
-
# signature_hash is computed and passed along with the response.
|
414
|
-
# After creating an unsigned transaction, one just needs to sign the sig_hashes
|
415
|
-
# and send everything to #assemble_tx, to receive the complete transaction that
|
416
|
-
# can be relayed to the network.
|
417
|
-
def handle_create_tx params = {}
|
418
|
-
params[:fee] ||= 0
|
419
|
-
#keys, recipients, fee = 0
|
420
|
-
keystore = Bitcoin::Wallet::SimpleKeyStore.new(file: StringIO.new("[]"))
|
421
|
-
params[:keys].each do |k|
|
422
|
-
begin
|
423
|
-
key = Bitcoin::Key.from_base58(k)
|
424
|
-
key = { addr: key.addr, key: key }
|
425
|
-
rescue
|
426
|
-
if Bitcoin.valid_address?(k)
|
427
|
-
key = { addr: k }
|
428
|
-
else
|
429
|
-
begin
|
430
|
-
key = Bitcoin::Key.new(nil, k)
|
431
|
-
key = { addr: key.addr, key: key }
|
432
|
-
rescue
|
433
|
-
return { error: "Input not valid address, pub- or privkey: #{k}" }
|
434
|
-
end
|
435
|
-
end
|
436
|
-
end
|
437
|
-
keystore.add_key(key)
|
438
|
-
end
|
439
|
-
wallet = Bitcoin::Wallet::Wallet.new(@node.store, keystore)
|
440
|
-
|
441
|
-
tx = wallet.new_tx(params[:recipients].map {|r| [:address, r[0], r[1]]}, params[:fee])
|
442
|
-
return { error: "Error creating tx." } unless tx
|
443
|
-
{ hash: tx.hash, hex: tx.to_payload.hth,
|
444
|
-
missing_sigs: tx.in.map {|i| [i.sig_hash.hth, i.sig_address] rescue nil } }
|
445
|
-
rescue
|
446
|
-
{ error: "Error creating tx: #{$!.message}" }
|
447
|
-
p $!; puts *$@
|
448
|
-
end
|
449
|
-
|
450
|
-
# Assemble an unsigned transaction from the +tx+ and +sig_pubkeys+ params.
|
451
|
-
# The +tx+ is the regular transaction structure, with empty input scripts
|
452
|
-
# (as returned by #create_tx when called without privkeys).
|
453
|
-
# +sig_pubkeys+ is an array of [signature, pubkey] pairs used to build the
|
454
|
-
# input scripts.
|
455
|
-
def handle_assemble_tx params = {}
|
456
|
-
# tx_hex, sig_pubs
|
457
|
-
tx = Bitcoin::P::Tx.new(params[:tx].htb)
|
458
|
-
params[:sig_pubs].each.with_index do |sig_pub, idx|
|
459
|
-
sig, pub = *sig_pub.map(&:htb)
|
460
|
-
script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, pub)
|
461
|
-
tx.in[idx].script_sig_length = script_sig.bytesize
|
462
|
-
tx.in[idx].script_sig = script_sig
|
463
|
-
end
|
464
|
-
tx = Bitcoin::P::Tx.new(tx.to_payload)
|
465
|
-
tx.validator(@node.store).validate(raise_errors: true)
|
466
|
-
{ hash: tx.hash, hex: tx.to_payload.hth }
|
467
|
-
rescue
|
468
|
-
{ error: "Error assembling tx: #{$!.message}" }
|
469
|
-
p $!; puts *$@
|
470
|
-
end
|
471
|
-
|
472
|
-
# Relay given transaction (in hex).
|
473
|
-
# bitcoin_node relay_tx <tx in hex>
|
474
|
-
def handle_relay_tx request, params = {}
|
475
|
-
params[:send] ||= 3
|
476
|
-
params[:wait] ||= 3
|
477
|
-
# request, hex, send = 3, wait = 3
|
478
|
-
begin
|
479
|
-
tx = Bitcoin::P::Tx.new(params[:hex].htb)
|
480
|
-
rescue
|
481
|
-
return respond(request, { error: "Error decoding transaction." })
|
482
|
-
end
|
483
|
-
|
484
|
-
validator = tx.validator(@node.store)
|
485
|
-
unless validator.validate(rules: [:syntax])
|
486
|
-
return respond(request, { error: "Transaction syntax invalid.",
|
487
|
-
details: validator.error })
|
488
|
-
end
|
489
|
-
unless validator.validate(rules: [:context])
|
490
|
-
return respond(request, { error: "Transaction context invalid.",
|
491
|
-
details: validator.error })
|
492
|
-
end
|
493
|
-
|
494
|
-
#@node.store.store_tx(tx)
|
495
|
-
@node.relay_tx[tx.hash] = tx
|
496
|
-
@node.relay_propagation[tx.hash] = 0
|
497
|
-
@node.connections.select(&:connected?).sample(params[:send]).each {|c| c.send_inv(:tx, tx.hash) }
|
498
|
-
|
499
|
-
EM.add_timer(params[:wait]) do
|
500
|
-
received = @node.relay_propagation[tx.hash]
|
501
|
-
total = @node.connections.select(&:connected?).size - params[:send]
|
502
|
-
percent = 100.0 / total * received
|
503
|
-
respond(request, { success: true, hash: tx.hash, propagation: {
|
504
|
-
received: received, sent: 1, percent: percent } })
|
505
|
-
end
|
506
|
-
rescue
|
507
|
-
respond(request, { error: $!.message, backtrace: $@ })
|
508
|
-
p $!; puts *$@
|
509
|
-
end
|
510
|
-
|
511
|
-
# Stop the bitcoin node.
|
512
|
-
# bitcoin_node stop
|
513
|
-
def handle_stop
|
514
|
-
Thread.start { sleep 0.1; @node.stop }
|
515
|
-
{ state: :stopping }
|
516
|
-
end
|
517
|
-
|
518
|
-
# List all available commands.
|
519
|
-
# bitcoin_node help
|
520
|
-
def handle_help
|
521
|
-
self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
|
522
|
-
end
|
523
|
-
|
524
|
-
# Validate and store given block (in hex) as if it was received by a peer.
|
525
|
-
# { method: "store_block", params: { hex: <block data in hex> } }
|
526
|
-
def handle_store_block params
|
527
|
-
block = Bitcoin::P::Block.new(params[:hex].htb)
|
528
|
-
@node.queue << [:block, block]
|
529
|
-
{ queued: block.hash }
|
530
|
-
end
|
531
|
-
|
532
|
-
# Store given transaction (in hex) as if it was received by a peer.
|
533
|
-
# { method: "store_tx", params: { hex: <tx data in hex> } }
|
534
|
-
def handle_store_tx params
|
535
|
-
tx = Bitcoin::P::Tx.new(params[:hex].htb)
|
536
|
-
@node.queue << [:tx, tx]
|
537
|
-
{ queued: tx.hash }
|
538
|
-
end
|
539
|
-
|
540
|
-
# # format node uptime
|
541
|
-
# def format_uptime t
|
542
|
-
# mm, ss = t.divmod(60) #=> [4515, 21]
|
543
|
-
# hh, mm = mm.divmod(60) #=> [75, 15]
|
544
|
-
# dd, hh = hh.divmod(24) #=> [3, 3]
|
545
|
-
# "%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
|
546
|
-
# end
|
547
|
-
|
548
|
-
# disconnect notification clients when connection is closed
|
549
|
-
def unbind
|
550
|
-
#@node.notifiers.unsubscribe(@notify_sid) if @notify_sid
|
551
|
-
@node.command_connections.delete(self)
|
552
|
-
end
|
553
|
-
|
554
|
-
private
|
555
|
-
|
556
|
-
def symbolize_keys(obj)
|
557
|
-
return obj unless [Hash, Array].include?(obj.class)
|
558
|
-
return obj.map {|v| symbolize_keys(v) } if obj.is_a?(Array)
|
559
|
-
obj.inject({}){|result, (key, value)|
|
560
|
-
new_key = key.is_a?(String) ? key.to_sym : key
|
561
|
-
new_value = case value
|
562
|
-
when Hash then symbolize_keys(value)
|
563
|
-
when Array then value.map {|v| symbolize_keys(v) }
|
564
|
-
else value; end
|
565
|
-
result[new_key] = new_value
|
566
|
-
result
|
567
|
-
}
|
568
|
-
end
|
569
|
-
|
570
|
-
end
|