bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/bin/bitcoin_gui
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift( File.expand_path("../../lib", __FILE__) )
|
3
|
+
|
4
|
+
require 'bitcoin'
|
5
|
+
require 'optparse'
|
6
|
+
|
7
|
+
Bitcoin.require_dependency 'ffi-gtk3', gem: "gir_ffi"
|
8
|
+
Bitcoin.require_dependency :eventmachine
|
9
|
+
Bitcoin.require_dependency :notify
|
10
|
+
Bitcoin.require_dependency :json
|
11
|
+
|
12
|
+
|
13
|
+
defaults = {
|
14
|
+
:network => "bitcoin",
|
15
|
+
:storage => "dummy",
|
16
|
+
:keystore => "simple::file=~/.bitcoin-ruby/keys.json",
|
17
|
+
:command => "127.0.0.1:9999"
|
18
|
+
}
|
19
|
+
options = Bitcoin::Config.load(defaults, :wallet)
|
20
|
+
|
21
|
+
optparse = OptionParser.new do |opts|
|
22
|
+
opts.banner =
|
23
|
+
"Usage: bitcoin_wallet [options] <command> [<command options>]\n"
|
24
|
+
|
25
|
+
opts.separator("\nAvailable options:\n")
|
26
|
+
|
27
|
+
opts.on("-c", "--config FILE",
|
28
|
+
"Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
|
29
|
+
options = Bitcoin::Config.load_file(options, file, :wallet)
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-n", "--network NETWORK",
|
33
|
+
"User Network (default: #{options[:network]})") do |network|
|
34
|
+
options[:network] = network
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-s", "--storage BACKEND::CONFIG",
|
38
|
+
"Use storage backend (default: #{options[:storage]})") do |storage|
|
39
|
+
options[:storage] = storage
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on("--command [HOST:PORT]",
|
43
|
+
"Node command socket (default: #{options[:command]})") do |command|
|
44
|
+
options[:command] = command
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-k", "--keystore [backend::<config>]",
|
48
|
+
"Key store (default: #{options[:store]})") do |store|
|
49
|
+
options[:keystore] = store
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-h", "--help", "Display this help") do
|
53
|
+
puts opts; exit
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.separator "\nAvailable commands:\n" +
|
57
|
+
" balance [<addr>] - display balance for given addr or whole wallet\n" +
|
58
|
+
" list <addr> - list transaction history for address\n" +
|
59
|
+
" send <addr>:<amount>[,<addr>:<amount>...] [<fee>] - send transaction\n" +
|
60
|
+
" new - generate new key and add to keystore\n" +
|
61
|
+
" import <base58> - import key in base58 format\n" +
|
62
|
+
" export <addr> - export key to base58 format\n"
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
optparse.parse!
|
67
|
+
|
68
|
+
Bitcoin.network = options[:network]
|
69
|
+
|
70
|
+
backend, config = options[:storage].split("::")
|
71
|
+
@storage = Bitcoin::Storage.send(backend, :db => config)
|
72
|
+
|
73
|
+
backend, config = options[:keystore].split("::")
|
74
|
+
config = Hash[config.split(",").map{|c| c.split("=")}]
|
75
|
+
|
76
|
+
EM.run do
|
77
|
+
@gui = Bitcoin::Gui::Gui.new(@storage, config['file'])
|
78
|
+
Bitcoin::Gui::Connection.new(*options[:command].split(":"), @gui)
|
79
|
+
EM.gtk_main
|
80
|
+
end
|
data/bin/bitcoin_node
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift( File.expand_path("../../lib", __FILE__) )
|
3
|
+
|
4
|
+
require 'bitcoin'
|
5
|
+
require 'optparse'
|
6
|
+
Bitcoin.require_dependency :eventmachine
|
7
|
+
Bitcoin.require_dependency :json
|
8
|
+
|
9
|
+
defaults = {
|
10
|
+
:network => "bitcoin",
|
11
|
+
:command => "127.0.0.1:9999",
|
12
|
+
:listen => "0.0.0.0:8332",
|
13
|
+
:connect => "",
|
14
|
+
:storage => "sequel::sqlite://bitcoin.db",
|
15
|
+
:headers_only => false,
|
16
|
+
:dns => true,
|
17
|
+
:epoll => false,
|
18
|
+
:epoll_limit => 10000,
|
19
|
+
:epoll_user => nil,
|
20
|
+
:log => {
|
21
|
+
:network => :info,
|
22
|
+
:storage => :info,
|
23
|
+
},
|
24
|
+
:max => {
|
25
|
+
:connections => 32,
|
26
|
+
:addr => 1024,
|
27
|
+
:queue => 500,
|
28
|
+
:inv => 500,
|
29
|
+
},
|
30
|
+
:intervals => {
|
31
|
+
:queue => 5,
|
32
|
+
:inv_queue => 5,
|
33
|
+
:addrs => 15,
|
34
|
+
:connect => 30,
|
35
|
+
},
|
36
|
+
:daemon => false,
|
37
|
+
}
|
38
|
+
|
39
|
+
options = Bitcoin::Config.load(defaults, :blockchain)
|
40
|
+
|
41
|
+
optparse = OptionParser.new do |opts|
|
42
|
+
opts.banner = "Usage: bitcoin_node [options]"
|
43
|
+
|
44
|
+
opts.separator("\nAvailable options:\n")
|
45
|
+
|
46
|
+
opts.on("-c", "--config FILE",
|
47
|
+
"Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
|
48
|
+
options = Bitcoin::Config.load_file(options, file, :blockchain)
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-n", "--network [NETWORK]",
|
52
|
+
"User Network (default: #{options[:network]})") do |network|
|
53
|
+
options[:network] = network
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("--command [HOST:PORT]",
|
57
|
+
"Command socket (default: #{options[:command]})") do |command|
|
58
|
+
options[:command] = command
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on("-l", "--listen [HOST:PORT]",
|
62
|
+
"Listen address/port (default: #{options[:listen]})") do |listen|
|
63
|
+
options[:listen] = listen
|
64
|
+
end
|
65
|
+
|
66
|
+
opts.on("--connect [HOST:PORT[,HOST:PORT[...]]]",
|
67
|
+
"Hosts to connect to (default: #{options[:connect]})") do |connect|
|
68
|
+
options[:connect] = connect
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on("-s", "--storage [BACKEND::CONFIG]",
|
72
|
+
"Use storage backend (default: #{options[:storage]})") do |storage|
|
73
|
+
options[:storage] = storage
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("--ho", "--headers-only",
|
77
|
+
"Download only block headers") do
|
78
|
+
options[:headers_only] = true
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on("-d", "--dns", "Use DNS seeds (default)") do
|
82
|
+
options[:dns] = true
|
83
|
+
end
|
84
|
+
|
85
|
+
opts.on("--nd", "--no-dns", "Don't use DNS seeds") do
|
86
|
+
options[:dns] = false
|
87
|
+
end
|
88
|
+
|
89
|
+
opts.on("--epoll", "Enable epoll support") do
|
90
|
+
options[:epoll] = true
|
91
|
+
end
|
92
|
+
|
93
|
+
opts.on("--epoll-limit [NUM]",
|
94
|
+
"Increase socket descriptor limit to NUM") do |num|
|
95
|
+
options[:epoll_limit] = num.to_i
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on("--epoll-user [NAME]",
|
99
|
+
"Set effective user after increasing socket descriptor limit") do |user|
|
100
|
+
options[:epoll_user] = user
|
101
|
+
end
|
102
|
+
|
103
|
+
[:connections, :addr, :queue, :inv].each do |name|
|
104
|
+
opts.on("--m#{name.to_s[0]}", "--max-#{name} [COUNT]",
|
105
|
+
"Max #{name} (default: #{options[:max][name]})") do |count|
|
106
|
+
options[:max][name] = count.to_i
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
[:queue, :inv_queue, :blocks, :addrs, :connect].each do |name|
|
111
|
+
opts.on("--i#{name.to_s[0]}", "--interval-#{name} [SECONDS]",
|
112
|
+
"Interval for #{name} worker (default: #{options[:intervals][name]})") do |sec|
|
113
|
+
options[:intervals][name] = sec.to_i
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
[:network, :storage].each do |name|
|
118
|
+
opts.on("--l#{name.to_s[0]}", "--log-#{name} [LEVEL]",
|
119
|
+
"Log level for #{name} (default: #{options[:log][name]})") do |level|
|
120
|
+
options[:log][name] = level.to_sym
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
opts.on("-v", "--verbose", "Set all loggers to debug") do
|
125
|
+
options[:log].each_key {|k| options[:log][k] = :debug }
|
126
|
+
end
|
127
|
+
|
128
|
+
opts.on("-q", "--quiet", "Set all loggers to warn") do
|
129
|
+
options[:log].each_key {|k| options[:log][k] = :warn }
|
130
|
+
end
|
131
|
+
|
132
|
+
opts.on( '-h', '--help', 'Display this screen' ) do
|
133
|
+
puts opts; exit
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
optparse.parse!
|
138
|
+
|
139
|
+
Bitcoin.network = options[:network]
|
140
|
+
options[:command] = options[:command] == "" ? nil : options[:command].split(':')
|
141
|
+
options[:listen] = options[:listen] == "" ? nil : options[:listen].split(':')
|
142
|
+
options[:connect] = options[:connect] == "" ? [] : options[:connect].split(',').map{|h| h.split(':')}
|
143
|
+
|
144
|
+
if ARGV.any?
|
145
|
+
EM.run do
|
146
|
+
Bitcoin::Network::CommandClient.connect(*options[:command]) do
|
147
|
+
on_response do |cmd, data|
|
148
|
+
unless cmd == "monitor"
|
149
|
+
puts JSON.pretty_generate data
|
150
|
+
EM.stop
|
151
|
+
end
|
152
|
+
end
|
153
|
+
on_block do |block, depth|
|
154
|
+
puts "block: #{block['hash']} (#{depth})"
|
155
|
+
end
|
156
|
+
on_tx do |tx|
|
157
|
+
puts "tx: #{tx['hash']}"
|
158
|
+
end
|
159
|
+
on_connection do |type, host|
|
160
|
+
if type == "connected"
|
161
|
+
puts "Connected: #{host['host']}:#{host['port']}"
|
162
|
+
else
|
163
|
+
puts "Disconnected: #{host.inspect}"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
on_connected do
|
167
|
+
request(ARGV[0], *(ARGV[1] || "").split(" "))
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
else
|
172
|
+
node = Bitcoin::Network::Node.new(options)
|
173
|
+
node.run
|
174
|
+
end
|
data/bin/bitcoin_shell
ADDED
data/bin/bitcoin_wallet
ADDED
@@ -0,0 +1,323 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.unshift( File.expand_path("../../lib", __FILE__) )
|
3
|
+
|
4
|
+
require 'bitcoin'
|
5
|
+
require 'eventmachine'
|
6
|
+
require 'optparse'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
defaults = {
|
10
|
+
:network => "testnet",
|
11
|
+
:storage => "dummy",
|
12
|
+
:keystore => "simple::file=#{ENV['HOME']}/.bitcoin-ruby/keys.json",
|
13
|
+
:command => "127.0.0.1:9999"
|
14
|
+
}
|
15
|
+
options = Bitcoin::Config.load(defaults, :wallet)
|
16
|
+
|
17
|
+
optparse = OptionParser.new do |opts|
|
18
|
+
opts.banner =
|
19
|
+
"Usage: bitcoin_wallet [options] <command> [<command options>]\n"
|
20
|
+
|
21
|
+
opts.separator("\nAvailable options:\n")
|
22
|
+
|
23
|
+
opts.on("-c", "--config FILE",
|
24
|
+
"Config file (default: #{Bitcoin::Config::CONFIG_PATHS})") do |file|
|
25
|
+
options = Bitcoin::Config.load_file(options, file, :wallet)
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-n", "--network NETWORK",
|
29
|
+
"User Network (default: #{options[:network]})") do |network|
|
30
|
+
options[:network] = network
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-s", "--storage BACKEND::CONFIG",
|
34
|
+
"Use storage backend (default: #{options[:storage]})") do |storage|
|
35
|
+
options[:storage] = storage
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on("--command [HOST:PORT]",
|
39
|
+
"Node command socket (default: #{options[:command]})") do |command|
|
40
|
+
options[:command] = command
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-k", "--keystore [backend::<config>]",
|
44
|
+
"Key store (default: #{options[:store]})") do |store|
|
45
|
+
options[:keystore] = store.gsub("~", ENV['HOME'])
|
46
|
+
end
|
47
|
+
|
48
|
+
opts.on("-h", "--help", "Display this help") do
|
49
|
+
puts opts; exit
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.separator "\nAvailable commands:\n" +
|
53
|
+
" balance [<addr>] - display balance for given addr or whole wallet\n" +
|
54
|
+
" list <addr> - list transaction history for address\n" +
|
55
|
+
" send <addr>:<amount>[,<addr>:<amount>...] [<fee>] - send transaction\n" +
|
56
|
+
" new - generate new key and add to keystore\n" +
|
57
|
+
" import <base58> - import key in base58 format\n" +
|
58
|
+
" export <addr> - export key to base58 format\n"
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
optparse.parse!
|
63
|
+
|
64
|
+
cmd = ARGV.shift; cmdopts = ARGV
|
65
|
+
unless cmd
|
66
|
+
exit puts optparse
|
67
|
+
end
|
68
|
+
|
69
|
+
Bitcoin.network = options[:network]
|
70
|
+
backend, config = options[:keystore].split("::")
|
71
|
+
config = Hash[config.split(",").map{|c| c.split("=")}]
|
72
|
+
keystore = Bitcoin::Wallet.const_get("#{backend.capitalize}KeyStore").new(config)
|
73
|
+
if backend == "deterministic" && !config["nonce"]
|
74
|
+
puts "nonce: #{keystore.generator.nonce}"
|
75
|
+
end
|
76
|
+
#puts *keystore.get_keys.map(&:addr)
|
77
|
+
backend, config = options[:storage].split("::")
|
78
|
+
storage = Bitcoin::Storage.send(backend, :db => config)
|
79
|
+
|
80
|
+
|
81
|
+
wallet = Bitcoin::Wallet::Wallet.new(storage, keystore, Bitcoin::Wallet::SimpleCoinSelector)
|
82
|
+
|
83
|
+
|
84
|
+
def str_val(val, pre='')
|
85
|
+
("#{pre}%.8f" % (val / 1e8)).rjust(15)
|
86
|
+
end
|
87
|
+
|
88
|
+
def val_str(str)
|
89
|
+
(str.to_f * 1e8).to_i
|
90
|
+
end
|
91
|
+
|
92
|
+
case cmd
|
93
|
+
when "balance"
|
94
|
+
if cmdopts && cmdopts.size == 1
|
95
|
+
addr = cmdopts[0]
|
96
|
+
balance = storage.get_balance(Bitcoin.hash160_from_address(addr))
|
97
|
+
puts "#{addr} balance: #{str_val balance}"
|
98
|
+
else
|
99
|
+
puts "Total balance: #{str_val wallet.get_balance}"
|
100
|
+
end
|
101
|
+
|
102
|
+
when "new"
|
103
|
+
puts "Generated new key with address: #{wallet.get_new_addr}"
|
104
|
+
|
105
|
+
when "add"
|
106
|
+
key = {:label => ARGV[2]}
|
107
|
+
case ARGV[0]
|
108
|
+
when "pub"
|
109
|
+
k = Bitcoin::Key.new(nil, ARGV[1])
|
110
|
+
key[:key] = k
|
111
|
+
key[:addr] = k.addr
|
112
|
+
when "priv"
|
113
|
+
k = Bitcoin::Key.new(ARGV[1], nil)
|
114
|
+
k.regenerate_pubkey
|
115
|
+
key[:key] = k
|
116
|
+
key[:addr] = k.addr
|
117
|
+
when "addr"
|
118
|
+
key[:addr] = ARGV[1]
|
119
|
+
else
|
120
|
+
raise "unknown type #{ARGV[0]}"
|
121
|
+
end
|
122
|
+
wallet.add_key key
|
123
|
+
|
124
|
+
when "label"
|
125
|
+
wallet.label(ARGV[0], ARGV[1])
|
126
|
+
|
127
|
+
when "flag"
|
128
|
+
wallet.flag(ARGV[0], *ARGV[1].split("="))
|
129
|
+
|
130
|
+
when "key"
|
131
|
+
key = wallet.keystore.key(ARGV[0])
|
132
|
+
puts "Label: #{key[:label]}"
|
133
|
+
puts "Address: #{key[:addr]}"
|
134
|
+
puts "Pubkey: #{key[:key].pub}"
|
135
|
+
puts "Privkey: #{key[:key].priv}" if ARGV[1] == '-p'
|
136
|
+
puts "Mine: #{key[:mine]}"
|
137
|
+
|
138
|
+
|
139
|
+
when "import"
|
140
|
+
if wallet.keystore.respond_to?(:import)
|
141
|
+
addr = wallet.keystore.import(cmdopts[0])
|
142
|
+
puts "Key for #{addr} imported."
|
143
|
+
else
|
144
|
+
puts "Keystore doesn't support importing."
|
145
|
+
end
|
146
|
+
|
147
|
+
when "export"
|
148
|
+
base58 = wallet.keystore.export(cmdopts[0])
|
149
|
+
puts "Base58 encoded private key for #{cmdopts[0]}:"
|
150
|
+
puts base58
|
151
|
+
|
152
|
+
when "list"
|
153
|
+
if cmdopts && cmdopts.size == 1
|
154
|
+
depth = storage.get_depth
|
155
|
+
total = 0
|
156
|
+
key = wallet.keystore.key(cmdopts[0])
|
157
|
+
storage.get_txouts_for_address(key[:addr]).each do |txout|
|
158
|
+
total += txout.value
|
159
|
+
tx = txout.get_tx
|
160
|
+
blocks = depth - tx.get_block.depth rescue 0
|
161
|
+
puts "#{tx.hash} | #{str_val txout.value, '+ '} | " +
|
162
|
+
"#{str_val total} | #{blocks}"
|
163
|
+
tx.in.map(&:get_prev_out).each do |prev_out|
|
164
|
+
if prev_out
|
165
|
+
puts " <- #{prev_out.get_address}"
|
166
|
+
else
|
167
|
+
puts " <- generation"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
puts
|
171
|
+
|
172
|
+
if txin = txout.get_next_in
|
173
|
+
tx = txin.get_tx
|
174
|
+
total -= txout.value
|
175
|
+
blocks = depth - tx.get_block.depth rescue 0
|
176
|
+
puts "#{tx.hash} | #{str_val txout.value, '- '} | " +
|
177
|
+
"#{str_val total} | #{blocks}"
|
178
|
+
txin.get_tx.out.each do |out|
|
179
|
+
puts " -> #{out.get_addresses.join(', ')}"
|
180
|
+
end
|
181
|
+
puts
|
182
|
+
end
|
183
|
+
end
|
184
|
+
puts "Total balance: #{str_val total}"
|
185
|
+
else
|
186
|
+
puts "Wallet addresses:"
|
187
|
+
total = 0
|
188
|
+
wallet.list.each do |key, balance|
|
189
|
+
total += balance
|
190
|
+
icon = key[:key] && key[:key].priv ? "P" : (key[:mine] ? "M" : " ")
|
191
|
+
puts " #{icon} #{key[:label].to_s.ljust(10)} (#{key[:addr].to_s.ljust(34)}) - #{("%.8f" % (balance / 1e8)).rjust(15)}"
|
192
|
+
end
|
193
|
+
puts "Total balance: #{str_val wallet.get_balance}"
|
194
|
+
end
|
195
|
+
|
196
|
+
when "send"
|
197
|
+
to = cmdopts[0].split(',').map do |pair|
|
198
|
+
type, *addrs, value = pair.split(":")
|
199
|
+
value = val_str(value)
|
200
|
+
[type.to_sym, *addrs, value]
|
201
|
+
end
|
202
|
+
fee = val_str(cmdopts[1]) || 0
|
203
|
+
value = val_str value
|
204
|
+
|
205
|
+
unless wallet.get_balance >= (to.map{|t|t[-1]}.inject{|a,b|a+=b;a} + fee)
|
206
|
+
puts "Insufficient funds."; exit
|
207
|
+
end
|
208
|
+
|
209
|
+
tx = wallet.new_tx(to, fee)
|
210
|
+
|
211
|
+
if tx.is_a?(Bitcoin::Wallet::TxDP)
|
212
|
+
puts "Transaction needs to be signed by additional keys."
|
213
|
+
print "Filename to save TxDP: [./#{tx.id}.txdp] "
|
214
|
+
$stdout.flush
|
215
|
+
filename = $stdin.gets.strip
|
216
|
+
filename = "./#{tx.id}.txdp" if filename == ""
|
217
|
+
File.open(filename, "w") {|f| f.write(tx.serialize) }
|
218
|
+
exit
|
219
|
+
end
|
220
|
+
|
221
|
+
unless tx
|
222
|
+
puts "Error creating tx."; exit
|
223
|
+
end
|
224
|
+
|
225
|
+
total = 0
|
226
|
+
puts "Hash: #{tx.hash}"
|
227
|
+
puts "inputs:"
|
228
|
+
tx.in.each do |txin|
|
229
|
+
prev_out = storage.get_txout_for_txin(txin)
|
230
|
+
total += prev_out.value
|
231
|
+
puts " #{prev_out.get_address} - #{str_val prev_out.value}"
|
232
|
+
end
|
233
|
+
|
234
|
+
puts "outputs:"
|
235
|
+
tx.out.each do |txout|
|
236
|
+
total -= txout.value
|
237
|
+
script = Bitcoin::Script.new(txout.pk_script)
|
238
|
+
if script.is_pubkey?
|
239
|
+
puts "#{str_val txout.value} #{script.get_pubkey} (pubkey)"
|
240
|
+
elsif script.is_hash160?
|
241
|
+
puts "#{str_val txout.value} #{script.get_address} (address)"
|
242
|
+
elsif script.is_multisig?
|
243
|
+
puts "#{str_val txout.value} #{script.get_addresses.join(' ')} (multisig)"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
puts "Fee: #{str_val total}"
|
247
|
+
|
248
|
+
$stdout.sync = true
|
249
|
+
print "Really send transaction? (y/N) " and $stdout.flush
|
250
|
+
unless $stdin.gets.chomp.downcase == 'y'
|
251
|
+
puts "Aborted."; exit
|
252
|
+
end
|
253
|
+
|
254
|
+
EM.run do
|
255
|
+
Bitcoin::Network::CommandClient.connect(*options[:command].split(":")) do
|
256
|
+
on_connected do
|
257
|
+
request(:relay_tx, tx)
|
258
|
+
end
|
259
|
+
on_relay_tx do
|
260
|
+
puts "Transaction #{tx.hash} relayed"
|
261
|
+
EM.stop
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
when "sign"
|
267
|
+
txt = File.read(ARGV[0])
|
268
|
+
txdp = Bitcoin::Wallet::TxDP.parse(txt)
|
269
|
+
puts txdp.tx[0].to_json
|
270
|
+
|
271
|
+
print "Really sign transaction? (y/N) " and $stdout.flush
|
272
|
+
unless $stdin.gets.chomp.downcase == 'y'
|
273
|
+
puts "Aborted."; exit
|
274
|
+
end
|
275
|
+
|
276
|
+
txdp.sign_inputs do |tx, prev_tx, i, addr|
|
277
|
+
key = keystore.key(addr)[:key] rescue nil
|
278
|
+
next nil unless key && !key.priv.nil?
|
279
|
+
sig_hash = tx.signature_hash_for_input(i, prev_tx)
|
280
|
+
sig = key.sign(sig_hash)
|
281
|
+
script_sig = Bitcoin::Script.to_pubkey_script_sig(sig, [key.pub].pack("H*"))
|
282
|
+
script_sig.unpack("H*")[0]
|
283
|
+
end
|
284
|
+
File.open(ARGV[0], "w") {|f| f.write txdp.serialize }
|
285
|
+
|
286
|
+
when "relay"
|
287
|
+
txt = File.read(ARGV[0])
|
288
|
+
txdp = Bitcoin::Wallet::TxDP.parse(txt)
|
289
|
+
tx = txdp.tx[0]
|
290
|
+
puts tx.to_json
|
291
|
+
txdp.inputs.each_with_index do |s, i|
|
292
|
+
value, sigs = *s
|
293
|
+
tx.in[i].script_sig = [sigs[0][1]].pack("H*")
|
294
|
+
end
|
295
|
+
tx.in.each_with_index do |txin, i|
|
296
|
+
p txdp.tx.map(&:hash)
|
297
|
+
prev_tx = storage.get_tx(txin.prev_out.reverse.unpack("H*")[0])
|
298
|
+
raise "prev tx #{txin.prev_out.reverse.unpack("H*")[0]} not found" unless prev_tx
|
299
|
+
raise "signature error" unless tx.verify_input_signature(i, prev_tx)
|
300
|
+
end
|
301
|
+
|
302
|
+
$stdout.sync = true
|
303
|
+
print "Really send transaction? (y/N) " and $stdout.flush
|
304
|
+
unless $stdin.gets.chomp.downcase == 'y'
|
305
|
+
puts "Aborted."; exit
|
306
|
+
end
|
307
|
+
|
308
|
+
EM.run do
|
309
|
+
EM.connect(*options[:command].split(":")) do |conn|
|
310
|
+
conn.send_data(["relay_tx", tx.to_payload.unpack("H*")[0]].to_json)
|
311
|
+
def conn.receive_data(data)
|
312
|
+
(@buf ||= BufferedTokenizer.new("\x00")).extract(data).each do |packet|
|
313
|
+
res = JSON.load(packet)
|
314
|
+
puts "Transaction relayed: #{res[1]["hash"]}"
|
315
|
+
EM.stop
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
else
|
322
|
+
puts "Unknown command."
|
323
|
+
end
|