bitcoin-ruby 0.0.1
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 +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Bitcoin::Gui
|
|
2
|
+
class TxView < TreeView
|
|
3
|
+
|
|
4
|
+
def initialize gui, replace = nil
|
|
5
|
+
super(gui, :tx_view, [
|
|
6
|
+
[GObject::TYPE_STRING, "Type"],
|
|
7
|
+
[GObject::TYPE_STRING, "Hash"],
|
|
8
|
+
[GObject::TYPE_STRING, "Value", :format_value_col],
|
|
9
|
+
[GObject::TYPE_INT, "Confirmations"],
|
|
10
|
+
[GObject::TYPE_STRING, "Direction"],
|
|
11
|
+
])
|
|
12
|
+
GObject.signal_connect(@view, "row-activated") do |view, path, column|
|
|
13
|
+
res, iter = @model.get_iter(path)
|
|
14
|
+
next unless res
|
|
15
|
+
tx_hash = @model.get_value(iter, 1).get_string
|
|
16
|
+
@gui.display_tx(tx_hash)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def update txouts
|
|
21
|
+
EM.defer do
|
|
22
|
+
@model.clear
|
|
23
|
+
txouts.each do |txout|
|
|
24
|
+
row = @model.append(nil)
|
|
25
|
+
@model.set_value(row, 0, txout.type.to_s)
|
|
26
|
+
@model.set_value(row, 1, txout.get_tx.hash)
|
|
27
|
+
@model.set_value(row, 2, txout.value.to_s)
|
|
28
|
+
@model.set_value(row, 3, txout.get_tx.confirmations)
|
|
29
|
+
@model.set_value(row, 4, "incoming")
|
|
30
|
+
if txin = txout.get_next_in
|
|
31
|
+
row = @model.append(nil)
|
|
32
|
+
@model.set_value(row, 0, txout.type.to_s)
|
|
33
|
+
@model.set_value(row, 1, txin.get_tx.hash)
|
|
34
|
+
@model.set_value(row, 2, (0 - txout.value).to_s)
|
|
35
|
+
@model.set_value(row, 3, txin.get_tx.confirmations)
|
|
36
|
+
@model.set_value(row, 4, "outgoing")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
@view.set_model @model
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class TxInView < TreeView
|
|
45
|
+
def initialize gui, replace = nil
|
|
46
|
+
super(gui, [
|
|
47
|
+
[GObject::TYPE_STRING, "Type"],
|
|
48
|
+
[GObject::TYPE_STRING, "From"],
|
|
49
|
+
[GObject::TYPE_STRING, "Value", :format_value_col]
|
|
50
|
+
])
|
|
51
|
+
old = @gui.builder.get_object("tx_view")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update txins
|
|
55
|
+
@model.clear
|
|
56
|
+
txins.each do |txin|
|
|
57
|
+
txout = txin.get_prev_out
|
|
58
|
+
row = @model.append(nil)
|
|
59
|
+
@model.set_value(row, 0, txout.type.to_s)
|
|
60
|
+
@model.set_value(row, 1, txout.get_addresses.join(", "))
|
|
61
|
+
@model.set_value(row, 2, txout.value.to_s)
|
|
62
|
+
end
|
|
63
|
+
@view.set_model @model
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
data/lib/bitcoin/key.rb
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
|
|
3
|
+
# Elliptic Curve key as used in bitcoin.
|
|
4
|
+
class Key
|
|
5
|
+
|
|
6
|
+
# Generate a new keypair.
|
|
7
|
+
# Bitcoin::Key.generate
|
|
8
|
+
def self.generate
|
|
9
|
+
k = new; k.generate; k
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Import private key from base58 fromat as described in
|
|
13
|
+
# https://en.bitcoin.it/wiki/Private_key#Base_58_Wallet_Import_format and
|
|
14
|
+
# https://en.bitcoin.it/wiki/Base58Check_encoding#Encoding_a_private_key.
|
|
15
|
+
# See also #to_base58
|
|
16
|
+
def self.from_base58(str)
|
|
17
|
+
hex = Bitcoin.decode_base58(str)
|
|
18
|
+
version, key, checksum = hex.unpack("a2a64a8")
|
|
19
|
+
raise "Invalid version" unless version == Bitcoin.network[:privkey_version]
|
|
20
|
+
raise "Invalid checksum" unless Bitcoin.checksum(version + key) == checksum
|
|
21
|
+
new(key)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def == other
|
|
25
|
+
self.priv == other.priv
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Create a new key with given +privkey+ and +pubkey+.
|
|
29
|
+
# Bitcoin::Key.new
|
|
30
|
+
# Bitcoin::Key.new(privkey)
|
|
31
|
+
# Bitcoin::Key.new(nil, pubkey)
|
|
32
|
+
def initialize privkey = nil, pubkey = nil
|
|
33
|
+
@key = Bitcoin.bitcoin_elliptic_curve
|
|
34
|
+
set_priv(privkey) if privkey
|
|
35
|
+
set_pub(pubkey) if pubkey
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Generate new priv/pub key.
|
|
39
|
+
def generate
|
|
40
|
+
@key.generate_key
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Get the private key (in hex).
|
|
44
|
+
def priv
|
|
45
|
+
return nil unless @key.private_key
|
|
46
|
+
@key.private_key.to_hex.rjust(64, '0')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Set the private key to +priv+ (in hex).
|
|
50
|
+
def priv= priv
|
|
51
|
+
set_priv(priv)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get the public key (in hex).
|
|
55
|
+
# In case the key was initialized with only
|
|
56
|
+
# a private key, the public key is regenerated.
|
|
57
|
+
def pub
|
|
58
|
+
unless @key.public_key
|
|
59
|
+
if @key.private_key
|
|
60
|
+
set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1])
|
|
61
|
+
else
|
|
62
|
+
return nil
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
@key.public_key.to_hex.rjust(130, '0')
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Set the public key (in hex).
|
|
69
|
+
def pub= pub
|
|
70
|
+
set_pub(pub)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Get the hash160 of the public key.
|
|
74
|
+
def hash160
|
|
75
|
+
Bitcoin.hash160(pub)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Get the address corresponding to the public key.
|
|
79
|
+
def addr
|
|
80
|
+
Bitcoin.hash160_to_address(hash160)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sign +data+ with the key.
|
|
84
|
+
# key1 = Bitcoin::Key.generate
|
|
85
|
+
# sig = key.sign("some data")
|
|
86
|
+
def sign(data)
|
|
87
|
+
@key.dsa_sign_asn1(data)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Verify signature +sig+ for +data+.
|
|
91
|
+
# key2 = Bitcoin::Key.new(nil, key1.pub)
|
|
92
|
+
# key2.verify("some data", sig)
|
|
93
|
+
def verify(data, sig)
|
|
94
|
+
@key.dsa_verify_asn1(data, sig)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Export private key to base58 format.
|
|
98
|
+
# See also Key.from_base58
|
|
99
|
+
def to_base58
|
|
100
|
+
data = Bitcoin.network[:privkey_version] + priv
|
|
101
|
+
hex = data + Bitcoin.checksum(data)
|
|
102
|
+
Bitcoin.int_to_base58( hex.to_i(16) )
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
protected
|
|
106
|
+
|
|
107
|
+
# Regenerate public key from the private key.
|
|
108
|
+
def regenerate_pubkey
|
|
109
|
+
set_pub(Bitcoin::OpenSSL_EC.regenerate_key(priv)[1])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Set +priv+ as the new private key (converting from hex).
|
|
113
|
+
def set_priv(priv)
|
|
114
|
+
@key.private_key = OpenSSL::BN.from_hex(priv)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Set +pub+ as the new public key (converting from hex).
|
|
118
|
+
def set_pub(pub)
|
|
119
|
+
@key.public_key = OpenSSL::PKey::EC::Point.from_hex(@key.group, pub)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
if Bitcoin.require_dependency :log4r, exit: false
|
|
2
|
+
# monkey-patch Log4r to accept level names as symbols
|
|
3
|
+
class Log4r::Logger
|
|
4
|
+
def level= l = 0
|
|
5
|
+
_level = l.is_a?(Fixnum) ? l : Log4r::LNAMES.index(l.to_s.upcase)
|
|
6
|
+
Log4r::Log4rTools.validate_level(_level)
|
|
7
|
+
@level = _level
|
|
8
|
+
LoggerFactory.define_methods(self)
|
|
9
|
+
Log4r::Logger.log_internal {"Logger '#{@fullname}' set to #{LNAMES[@level]}"}
|
|
10
|
+
@level
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module Bitcoin
|
|
16
|
+
# this is a very simple logger that is used if log4r is not available
|
|
17
|
+
module Logger
|
|
18
|
+
|
|
19
|
+
class Logger
|
|
20
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal]
|
|
21
|
+
|
|
22
|
+
attr_accessor :level
|
|
23
|
+
|
|
24
|
+
def initialize(name)
|
|
25
|
+
@name, @level = name, :info
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def level= level
|
|
29
|
+
@level = level.is_a?(Fixnum) ? LEVELS[level] : level.to_sym
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
LEVELS.each do |level|
|
|
33
|
+
define_method(level) do |*msg, &block|
|
|
34
|
+
return if LEVELS.index(level.to_sym) < LEVELS.index(@level.to_sym)
|
|
35
|
+
msg = block ? block.call : msg.join
|
|
36
|
+
puts "#{level.to_s.upcase.ljust(5)} #{@name}: #{msg}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# wrap a logger and prepend a special name in front of the messages
|
|
42
|
+
class LogWrapper
|
|
43
|
+
def initialize(name, log); @name, @log = name, log; end
|
|
44
|
+
def method_missing(m, *a, &blk)
|
|
45
|
+
@log.send(m, *a, &proc{ "#{@name} #{blk.call}" })
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# create a logger with given +name+. if log4r is installed, the logger
|
|
50
|
+
# will have a stdout and a fileout outputter to `log/<name>.log`.
|
|
51
|
+
# otherwise, the internal dummy logger is used which only logs to stdout.
|
|
52
|
+
def self.create name, level = :info
|
|
53
|
+
if defined?(Log4r)
|
|
54
|
+
@log = Log4r::Logger.new(name.to_s)
|
|
55
|
+
@log.level = level
|
|
56
|
+
@log.outputters << Log4r::Outputter.stdout
|
|
57
|
+
@log.outputters << Log4r::FileOutputter.new("fout", :filename => "log/#{name}.log")
|
|
58
|
+
else
|
|
59
|
+
@log = Bitcoin::Logger::Logger.new(name)
|
|
60
|
+
end
|
|
61
|
+
@log
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Client to connect to CommandHandler and issue requests or register for events
|
|
2
|
+
class Bitcoin::Network::CommandClient < EM::Connection
|
|
3
|
+
|
|
4
|
+
# create new client connecting to +host+:+port+ and executing callbacks from +block+,
|
|
5
|
+
# passing +args+ in.
|
|
6
|
+
# CommandClient.connect(host, port) do
|
|
7
|
+
# on_connected { request("info") }
|
|
8
|
+
# on_info {|i| p i}
|
|
9
|
+
# end
|
|
10
|
+
def initialize host, port, block, *args
|
|
11
|
+
@host, @port = host, port
|
|
12
|
+
@args = args
|
|
13
|
+
@callbacks = {}
|
|
14
|
+
@block = block
|
|
15
|
+
instance_eval &block if block
|
|
16
|
+
@buffer = BufferedTokenizer.new("\x00")
|
|
17
|
+
@connection_attempts = 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def log;
|
|
21
|
+
@log ||= Bitcoin::Logger.create(:client)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.connect host, port, *args, &block
|
|
25
|
+
client = EM.connect(host, port.to_i, self, host, port.to_i, block, *args)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# call +connected+ callback
|
|
29
|
+
def post_init
|
|
30
|
+
log.debug { "Connected" }
|
|
31
|
+
callback :connected
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# call +disconnected+ callback and try to reconnect
|
|
35
|
+
def unbind
|
|
36
|
+
log.debug { "Disconnected." }
|
|
37
|
+
callback :disconnected
|
|
38
|
+
if @connection_attempts > 1
|
|
39
|
+
log.info { "Trying to start server..." }
|
|
40
|
+
EM.defer { system("bin/bitcoin_node", "--quiet") }
|
|
41
|
+
end
|
|
42
|
+
EM.add_timer(1) do
|
|
43
|
+
@connection_attempts += 1
|
|
44
|
+
reconnect(@host, @port)
|
|
45
|
+
post_init
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# request command +cmd+ with +args+ from the server
|
|
50
|
+
def request cmd, *args
|
|
51
|
+
log.debug { "request: #{cmd} #{args.inspect}" }
|
|
52
|
+
register_monitor_callbacks if cmd.to_sym == :monitor
|
|
53
|
+
send_data([cmd, args].to_json + "\x00")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# receive response from server
|
|
57
|
+
def receive_data data
|
|
58
|
+
@connection_attempts = 0
|
|
59
|
+
@buffer.extract(data).each do |packet|
|
|
60
|
+
cmd, *data = *JSON.load(packet)
|
|
61
|
+
log.debug { d = data.inspect
|
|
62
|
+
"response: #{cmd} #{d[0...50]}#{d.size > 50 ? '...' : ''}" }
|
|
63
|
+
callback(:response, cmd, *data)
|
|
64
|
+
callback(cmd.to_sym, *data)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# call the callback specified by +name+ passing in +args+
|
|
69
|
+
def callback name, *args
|
|
70
|
+
cb = @callbacks[name.to_sym]
|
|
71
|
+
return unless cb
|
|
72
|
+
log.debug { "callback: #{name}" }
|
|
73
|
+
cb.call(*args)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# register callback methods
|
|
77
|
+
def method_missing(name, *args, &block)
|
|
78
|
+
if name =~ /^on_/
|
|
79
|
+
@callbacks[name.to_s.split("on_")[1].to_sym] = block
|
|
80
|
+
log.debug { "callback #{name} registered" }
|
|
81
|
+
else
|
|
82
|
+
super(name, *args)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# register callbacks for monitor
|
|
87
|
+
def register_monitor_callbacks
|
|
88
|
+
on_monitor do |type, data|
|
|
89
|
+
callback(type, *data)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'monitor'
|
|
3
|
+
|
|
4
|
+
# Started by the Node, accepts connections from CommandClient and answers requests or
|
|
5
|
+
# registers for events and notifies the clients when they happen.
|
|
6
|
+
class Bitcoin::Network::CommandHandler < EM::Connection
|
|
7
|
+
|
|
8
|
+
# create new CommandHandler
|
|
9
|
+
def initialize node
|
|
10
|
+
@node = node
|
|
11
|
+
@node.command_connections << self
|
|
12
|
+
@buf = BufferedTokenizer.new("\x00")
|
|
13
|
+
@lock = Monitor.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# wrap logger and append prefix
|
|
17
|
+
def log
|
|
18
|
+
@log ||= Bitcoin::Logger::LogWrapper.new("command:", @node.log)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# respond to a command; send serialized response to the client
|
|
22
|
+
def respond(cmd, data)
|
|
23
|
+
return unless data
|
|
24
|
+
@lock.synchronize do
|
|
25
|
+
send_data([cmd, data].to_json + "\x00")
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# receive request from the client
|
|
30
|
+
def receive_data data
|
|
31
|
+
@buf.extract(data).each do |packet|
|
|
32
|
+
cmd, args = JSON::parse(packet)
|
|
33
|
+
log.debug { [cmd, args] }
|
|
34
|
+
if respond_to?("handle_#{cmd}")
|
|
35
|
+
respond(cmd, send("handle_#{cmd}", *args))
|
|
36
|
+
else
|
|
37
|
+
respond(cmd, {:error => "unknown command: #{cmd}. send 'help' for help."})
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
rescue Exception
|
|
41
|
+
p $!
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# handle +monitor+ command; subscribe client to specified channels
|
|
45
|
+
# (+block+, +tx+, +connection+)
|
|
46
|
+
# bitcoin_node monitor block
|
|
47
|
+
# bitcoin_node monitor "block tx connection"
|
|
48
|
+
def handle_monitor *channels
|
|
49
|
+
channels.each do |channel|
|
|
50
|
+
@node.notifiers[channel.to_sym].subscribe do |*data|
|
|
51
|
+
respond("monitor", [channel, *data])
|
|
52
|
+
end
|
|
53
|
+
case channel.to_sym
|
|
54
|
+
when :block
|
|
55
|
+
head = Bitcoin::P::Block.new(@node.store.get_head.to_payload) rescue nil
|
|
56
|
+
respond("monitor", ["block", [head, @node.store.get_depth.to_s]]) if head
|
|
57
|
+
when :connection
|
|
58
|
+
@node.connections.select {|c| c.connected?}.each do |conn|
|
|
59
|
+
respond("monitor", [:connection, [:connected, conn.info]])
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# display various statistics
|
|
67
|
+
# bitcoin_node info
|
|
68
|
+
def handle_info
|
|
69
|
+
blocks = @node.connections.map(&:version).compact.map(&:block) rescue nil
|
|
70
|
+
{
|
|
71
|
+
:blocks => "#{@node.store.get_depth} (#{(blocks.inject{|a,b| a+=b;a} / blocks.size rescue '?')})#{@node.in_sync ? ' sync' : ''}",
|
|
72
|
+
:addrs => "#{@node.addrs.select{|a| a.alive?}.size} (#{@node.addrs.size})",
|
|
73
|
+
:connections => "#{@node.connections.select{|c| c.state == :connected}.size} (#{@node.connections.size})",
|
|
74
|
+
:queue => @node.queue.size,
|
|
75
|
+
:inv_queue => @node.inv_queue.size,
|
|
76
|
+
:inv_cache => @node.inv_cache.size,
|
|
77
|
+
:network => @node.config[:network],
|
|
78
|
+
:storage => @node.config[:storage],
|
|
79
|
+
:version => Bitcoin::Protocol::VERSION,
|
|
80
|
+
:uptime => format_uptime(@node.uptime),
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# display configuration hash currently used
|
|
85
|
+
# bitcoin_node config
|
|
86
|
+
def handle_config
|
|
87
|
+
@node.config
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# display connected peers
|
|
91
|
+
# bitcoin_node connections
|
|
92
|
+
def handle_connections
|
|
93
|
+
@node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
|
|
94
|
+
"#{c.host.rjust(15)}:#{c.port} [state: #{c.state}, " +
|
|
95
|
+
"version: #{c.version.version rescue '?'}, " +
|
|
96
|
+
"block: #{c.version.block rescue '?'}, " +
|
|
97
|
+
"uptime: #{format_uptime(c.uptime) rescue 0}, " +
|
|
98
|
+
"client: #{c.version.user_agent rescue '?'}]" }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# connect to given peer(s)
|
|
102
|
+
# bitcoin_node connect <ip>:<port>[,<ip>:<port>]
|
|
103
|
+
def handle_connect *args
|
|
104
|
+
args.each {|a| @node.connect_peer(*a.split(':')) }
|
|
105
|
+
{:state => "Connecting..."}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# disconnect peer(s)
|
|
109
|
+
# bitcoin_node disconnect <ip>:<port>[,<ip>,<port>]
|
|
110
|
+
def handle_disconnect *args
|
|
111
|
+
args.each do |c|
|
|
112
|
+
host, port = *c.split(":")
|
|
113
|
+
conn = @node.connections.select{|c| c.host == host && c.port == port.to_i}.first
|
|
114
|
+
conn.close_connection if conn
|
|
115
|
+
end
|
|
116
|
+
{:state => "Disconnected"}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# trigger node to ask peers for new blocks
|
|
120
|
+
# bitcoin_node getblocks
|
|
121
|
+
def handle_getblocks
|
|
122
|
+
@node.connections.sample.send_getblocks
|
|
123
|
+
{:state => "Sending getblocks..."}
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# trigger node to ask for new peer addrs
|
|
127
|
+
# bitcoin_node getaddr
|
|
128
|
+
def handle_getaddr
|
|
129
|
+
@node.connections.sample.send_getaddr
|
|
130
|
+
{:state => "Sending getaddr..."}
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# display known peer addrs (used by bin/bitcoin_dns_seed)
|
|
134
|
+
# bitcoin_node addrs [count]
|
|
135
|
+
def handle_addrs count = 32
|
|
136
|
+
@node.addrs.weighted_sample(count.to_i) do |addr|
|
|
137
|
+
Time.now.tv_sec + 7200 - addr.time
|
|
138
|
+
end.map do |addr|
|
|
139
|
+
[addr.ip, addr.port, Time.now.tv_sec - addr.time] rescue nil
|
|
140
|
+
end.compact
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# relay given transaction (in hex)
|
|
144
|
+
# bitcoin_node relay_tx <tx data>
|
|
145
|
+
def handle_relay_tx data
|
|
146
|
+
tx = Bitcoin::Protocol::Tx.from_hash(data)
|
|
147
|
+
@node.relay_tx(tx)
|
|
148
|
+
rescue
|
|
149
|
+
{:error => $!}
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# stop bitcoin node
|
|
153
|
+
# bitcoin_node stop
|
|
154
|
+
def handle_stop
|
|
155
|
+
Thread.start { sleep 0.1; @node.stop }
|
|
156
|
+
{:state => "Stopping..."}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# list all commands
|
|
160
|
+
# bitcoin_node help
|
|
161
|
+
def handle_help
|
|
162
|
+
self.methods.grep(/^handle_(.*?)/).map {|m| m.to_s.sub(/^(.*?)_/, '')}
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# format node uptime
|
|
166
|
+
def format_uptime t
|
|
167
|
+
mm, ss = t.divmod(60) #=> [4515, 21]
|
|
168
|
+
hh, mm = mm.divmod(60) #=> [75, 15]
|
|
169
|
+
dd, hh = hh.divmod(24) #=> [3, 3]
|
|
170
|
+
"%02d:%02d:%02d:%02d" % [dd, hh, mm, ss]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# disconnect notification clients when connection is closed
|
|
174
|
+
def unbind
|
|
175
|
+
#@node.notifiers.unsubscribe(@notify_sid) if @notify_sid
|
|
176
|
+
@node.command_connections.delete(self)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|