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,140 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'digest/sha2'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Bitcoin
|
|
6
|
+
module Protocol
|
|
7
|
+
|
|
8
|
+
autoload :TxIn, 'bitcoin/protocol/txin'
|
|
9
|
+
autoload :TxOut, 'bitcoin/protocol/txout'
|
|
10
|
+
autoload :Tx, 'bitcoin/protocol/tx'
|
|
11
|
+
autoload :Block, 'bitcoin/protocol/block'
|
|
12
|
+
autoload :Addr, 'bitcoin/protocol/address'
|
|
13
|
+
autoload :Alert, 'bitcoin/protocol/alert'
|
|
14
|
+
autoload :Version, 'bitcoin/protocol/version'
|
|
15
|
+
|
|
16
|
+
autoload :Handler, 'bitcoin/protocol/handler'
|
|
17
|
+
autoload :Parser, 'bitcoin/protocol/parser'
|
|
18
|
+
|
|
19
|
+
VERSION = 60001
|
|
20
|
+
|
|
21
|
+
DNS_Seed = [ "bitseed.xf2.org", "bitseed.bitcoin.org.uk" ]
|
|
22
|
+
Uniq = rand(0xffffffffffffffff)
|
|
23
|
+
|
|
24
|
+
def self.unpack_var_int(payload)
|
|
25
|
+
case payload.unpack("C")[0] # TODO add test cases
|
|
26
|
+
when 0xfd; payload.unpack("xva*")
|
|
27
|
+
when 0xfe; payload.unpack("xVa*")
|
|
28
|
+
when 0xff; payload.unpack("xQa*") # TODO add little-endian version of Q
|
|
29
|
+
else; payload.unpack("Ca*")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.pack_var_int(i)
|
|
34
|
+
if i < 0xfd; [ i].pack("C")
|
|
35
|
+
elsif i <= 0xffff; [0xfd, i].pack("Cv")
|
|
36
|
+
elsif i <= 0xffffffff; [0xfe, i].pack("CV")
|
|
37
|
+
elsif i <= 0xffffffffffffffff; [0xff, i].pack("CQ")
|
|
38
|
+
else raise "int(#{i}) too large!"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.unpack_var_string(payload)
|
|
43
|
+
size, payload = unpack_var_int(payload)
|
|
44
|
+
size > 0 ? (string, payload = payload.unpack("a#{size}a*")) : [nil, payload]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.pack_var_string(payload)
|
|
48
|
+
pack_var_int(payload.bytesize) + payload
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.unpack_var_string_array(payload) # unpacks set<string>
|
|
52
|
+
size, payload = unpack_var_int(payload)
|
|
53
|
+
return [nil, payload] if size == 0
|
|
54
|
+
[(0...size).map{ s, payload = unpack_var_string(payload); s }, payload]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def self.unpack_var_int_array(payload) # unpacks set<int>
|
|
58
|
+
size, payload = unpack_var_int(payload)
|
|
59
|
+
return [nil, payload] if size == 0
|
|
60
|
+
[(0...size).map{ i, payload = unpack_var_int(payload); i }, payload]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def self.pkt(command, payload)
|
|
65
|
+
cmd = command.ljust(12, "\x00")[0...12]
|
|
66
|
+
length = [payload.bytesize].pack("I")
|
|
67
|
+
checksum = Digest::SHA256.digest(Digest::SHA256.digest(payload))[0...4]
|
|
68
|
+
|
|
69
|
+
[Bitcoin.network[:magic_head], cmd, length, checksum, payload].join
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def self.version_pkt(from_id, from=nil, to=nil, last_block=nil, time=nil, user_agent=nil, version=nil)
|
|
73
|
+
opts = if from_id.is_a?(Hash)
|
|
74
|
+
from_id
|
|
75
|
+
else
|
|
76
|
+
STDERR.puts "Bitcoin::Protocol.version_pkt - API deprecated. please change it soon.."
|
|
77
|
+
{
|
|
78
|
+
:nonce => from_id, :from => from, :to => to, :last_block => last_block,
|
|
79
|
+
:time => time, :user_agent => user_agent, :version => version
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
version = Protocol::Version.new(opts)
|
|
83
|
+
version.to_pkt
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.ping_pkt(nonce = rand(0xffffffff))
|
|
87
|
+
pkt("ping", [nonce].pack("Q"))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def self.pong_pkt(nonce)
|
|
91
|
+
pkt("pong", [nonce].pack("Q"))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def self.verack_pkt
|
|
95
|
+
pkt("verack", "")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
TypeLookup = Hash[:tx, 1, :block, 2, nil, 0]
|
|
99
|
+
|
|
100
|
+
def self.getdata_pkt(type, hashes)
|
|
101
|
+
return if hashes.size >= 256
|
|
102
|
+
t = [ TypeLookup[type] ].pack("I")
|
|
103
|
+
pkt("getdata", [hashes.size].pack("C") + hashes.map{|hash| t + hash[0..32].reverse }.join)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.inv_pkt(type, hashes)
|
|
107
|
+
return if hashes.size >= 256
|
|
108
|
+
t = [ TypeLookup[type] ].pack("I")
|
|
109
|
+
pkt("inv", [hashes.size].pack("C") + hashes.map{|hash| t + hash[0..32].reverse }.join)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
DEFAULT_STOP_HASH = "00"*32
|
|
113
|
+
|
|
114
|
+
def self.locator_payload(locator_hashes, stop_hash)
|
|
115
|
+
payload = [
|
|
116
|
+
Bitcoin.network[:magic_head],
|
|
117
|
+
pack_var_int(locator_hashes.size),
|
|
118
|
+
locator_hashes.map{|l| htb(l).reverse }.join,
|
|
119
|
+
htb(stop_hash).reverse
|
|
120
|
+
].join
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def self.getblocks_pkt(locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
124
|
+
pkt "getblocks", locator_payload(locator_hashes, stop_hash)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def self.getheaders_pkt(locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
128
|
+
pkt "getheaders", locator_payload(locator_hashes, stop_hash)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.hth(h); h.unpack("H*")[0]; end
|
|
132
|
+
def self.htb(h); [h].pack("H*"); end
|
|
133
|
+
|
|
134
|
+
def self.read_binary_file(path)
|
|
135
|
+
File.open(path, 'rb'){|f| f.read }
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
P = Protocol
|
|
140
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Addr < Struct.new(:time, :service, :ip, :port)
|
|
5
|
+
|
|
6
|
+
# # IP Address / Port
|
|
7
|
+
# attr_reader :ip, :port
|
|
8
|
+
|
|
9
|
+
# # Time the node was last active
|
|
10
|
+
# attr_reader :time
|
|
11
|
+
|
|
12
|
+
# # Services supported by this node
|
|
13
|
+
# attr_reader :service
|
|
14
|
+
|
|
15
|
+
# create addr from raw binary +data+
|
|
16
|
+
def initialize(data = nil)
|
|
17
|
+
if data
|
|
18
|
+
self[:time], self[:service], self[:ip], self[:port] = data.unpack("IQx12a4n")
|
|
19
|
+
self[:ip] = ip.unpack("C*").join(".")
|
|
20
|
+
else
|
|
21
|
+
self[:time], self[:service] = Time.now.to_i, 1
|
|
22
|
+
self[:ip], self[:port] = "127.0.0.1", Bitcoin.network[:default_port]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# is this address alive?
|
|
27
|
+
def alive?
|
|
28
|
+
(Time.now.tv_sec-7200) <= self[:time]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_payload
|
|
32
|
+
ip = self[:ip].split(".").map(&:to_i)
|
|
33
|
+
[ time, service, ("\x00"*10)+"\xff\xff", *ip, port ].pack("IQa12C4n")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def string
|
|
37
|
+
"#{self[:ip]}:#{self[:port]}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.pkt(*addrs)
|
|
41
|
+
addrs = addrs.select{|i| i.is_a?(Bitcoin::Protocol::Addr) }
|
|
42
|
+
length = Bitcoin::Protocol.pack_var_int(addrs.size)
|
|
43
|
+
Bitcoin::Protocol.pkt("addr", length + addrs.map(&:to_payload).join)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Alert < Struct.new(:version, :relay_until, :expiration, :id, :cancel, :set_cancel,
|
|
5
|
+
:min_ver, :max_ver, :set_sub_ver, :priority, :comment, :status_bar, :reserved)
|
|
6
|
+
|
|
7
|
+
attr_accessor :payload, :signature
|
|
8
|
+
|
|
9
|
+
def initialize(values, alert_payload=nil, alert_signature=nil)
|
|
10
|
+
@payload, @signature = alert_payload, alert_signature
|
|
11
|
+
super(*values)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Valid_Keys = [ "04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284" ]
|
|
16
|
+
|
|
17
|
+
def valid_signature?
|
|
18
|
+
return false unless @payload && @signature
|
|
19
|
+
hash = Digest::SHA256.digest(Digest::SHA256.digest(@payload))
|
|
20
|
+
Valid_Keys.any?{|public_key| Bitcoin.verify_signature(hash, @signature, public_key) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def self.parse(payload)
|
|
25
|
+
count, payload = Bitcoin::Protocol.unpack_var_int(payload)
|
|
26
|
+
alert_payload, payload = payload.unpack("a#{count}a*")
|
|
27
|
+
count, payload = Bitcoin::Protocol.unpack_var_int(payload)
|
|
28
|
+
alert_signature, payload = payload.unpack("a#{count}a*")
|
|
29
|
+
|
|
30
|
+
version, relay_until, expiration, id, cancel, payload = alert_payload.unpack("VQQVVa*")
|
|
31
|
+
|
|
32
|
+
set_cancel, payload = Bitcoin::Protocol.unpack_var_int_array(payload)
|
|
33
|
+
min_ver, max_ver, payload = payload.unpack("VVa*")
|
|
34
|
+
set_sub_ver, payload = Bitcoin::Protocol.unpack_var_string_array(payload)
|
|
35
|
+
priority, payload = payload.unpack("Va*")
|
|
36
|
+
comment, payload = Bitcoin::Protocol.unpack_var_string(payload)
|
|
37
|
+
status_bar, payload = Bitcoin::Protocol.unpack_var_string(payload)
|
|
38
|
+
reserved, payload = Bitcoin::Protocol.unpack_var_string(payload)
|
|
39
|
+
|
|
40
|
+
values = [ version, relay_until, expiration, id, cancel, set_cancel, min_ver, max_ver, set_sub_ver, priority, comment, status_bar, reserved ]
|
|
41
|
+
|
|
42
|
+
new(values, alert_payload, alert_signature)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Block
|
|
5
|
+
|
|
6
|
+
# block hash
|
|
7
|
+
attr_accessor :hash
|
|
8
|
+
|
|
9
|
+
# previous block hash
|
|
10
|
+
attr_accessor :prev_block
|
|
11
|
+
|
|
12
|
+
# transactions (Array of Tx)
|
|
13
|
+
attr_accessor :tx
|
|
14
|
+
|
|
15
|
+
# merkle root
|
|
16
|
+
attr_accessor :mrkl_root
|
|
17
|
+
|
|
18
|
+
# block generation time
|
|
19
|
+
attr_accessor :time
|
|
20
|
+
|
|
21
|
+
# difficulty target bits
|
|
22
|
+
attr_accessor :bits
|
|
23
|
+
|
|
24
|
+
# nonce (number counted when searching for block hash matching target)
|
|
25
|
+
attr_accessor :nonce
|
|
26
|
+
|
|
27
|
+
# version (usually 1)
|
|
28
|
+
attr_accessor :ver
|
|
29
|
+
|
|
30
|
+
# raw protocol payload
|
|
31
|
+
attr_accessor :payload
|
|
32
|
+
|
|
33
|
+
alias :transactions :tx
|
|
34
|
+
|
|
35
|
+
# compare to another block
|
|
36
|
+
def ==(other)
|
|
37
|
+
@hash == other.hash
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# create block from raw binary +data+
|
|
41
|
+
def initialize(data)
|
|
42
|
+
@tx = []
|
|
43
|
+
parse_data(data) if data
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# parse raw binary data
|
|
47
|
+
def parse_data(data)
|
|
48
|
+
@ver, @prev_block, @mrkl_root, @time, @bits, @nonce, payload = data.unpack("Ia32a32IIIa*")
|
|
49
|
+
recalc_block_hash
|
|
50
|
+
|
|
51
|
+
tx_size, payload = Protocol.unpack_var_int(payload)
|
|
52
|
+
(0...tx_size).each{ break if payload == true
|
|
53
|
+
t = Tx.new(nil)
|
|
54
|
+
payload = t.parse_data(payload)
|
|
55
|
+
@tx << t
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@payload = to_payload
|
|
59
|
+
payload
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# recalculate the block hash
|
|
63
|
+
def recalc_block_hash
|
|
64
|
+
@hash = Bitcoin.block_hash(hth(@prev_block), hth(@mrkl_root), @time, @bits, @nonce, @ver)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# get the block header info
|
|
68
|
+
# [<version>, <prev_block>, <merkle_root>, <time>, <bits>, <nonce>, <txcount>, <size>]
|
|
69
|
+
def header_info
|
|
70
|
+
[@ver, hth(@prev_block), hth(@mrkl_root), Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def hth(h); h.reverse.unpack("H*")[0]; end
|
|
74
|
+
def htb(s); [s].pack('H*').reverse; end
|
|
75
|
+
|
|
76
|
+
# convert to raw binary format
|
|
77
|
+
def to_payload
|
|
78
|
+
head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("Ia32a32III")
|
|
79
|
+
[head, Protocol.pack_var_int(@tx.size), @tx.map(&:to_payload).join].join
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# convert to ruby hash (see also #from_hash)
|
|
83
|
+
def to_hash
|
|
84
|
+
{
|
|
85
|
+
'hash' => @hash, 'ver' => @ver,
|
|
86
|
+
'prev_block' => hth(@prev_block), 'mrkl_root' => hth(@mrkl_root),
|
|
87
|
+
'time' => @time, 'bits' => @bits, 'nonce' => @nonce,
|
|
88
|
+
'n_tx' => @tx.size, 'size' => (@payload||to_payload).bytesize,
|
|
89
|
+
'tx' => @tx.map{|i| i.to_hash },
|
|
90
|
+
'mrkl_tree' => Bitcoin.hash_mrkl_tree( @tx.map{|i| i.hash } )
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def hextarget
|
|
95
|
+
Bitcoin.decode_compact_bits(@bits)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def decimaltarget
|
|
99
|
+
Bitcoin.decode_compact_bits(@bits).to_i(16)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def difficulty
|
|
103
|
+
Bitcoin.block_difficulty(@bits)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# convert to json representation as seen in the block explorer.
|
|
107
|
+
# (see also #from_json)
|
|
108
|
+
def to_json(options = {:space => ''}, *a)
|
|
109
|
+
JSON.pretty_generate( to_hash, options )
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# write json representation to a file
|
|
113
|
+
# (see also #to_json)
|
|
114
|
+
def to_json_file(path)
|
|
115
|
+
File.open(path, 'wb'){|f| f.print to_json; }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# parse ruby hash (see also #to_hash)
|
|
119
|
+
def self.from_hash(h)
|
|
120
|
+
blk = new(nil)
|
|
121
|
+
blk.instance_eval{
|
|
122
|
+
@ver, @time, @bits, @nonce = h.values_at('ver', 'time', 'bits', 'nonce')
|
|
123
|
+
@prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| htb(i) }
|
|
124
|
+
recalc_block_hash
|
|
125
|
+
h['tx'].each{|tx| @tx << Tx.from_hash(tx) }
|
|
126
|
+
}
|
|
127
|
+
blk
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# convert ruby hash to raw binary
|
|
131
|
+
def self.binary_from_hash(h); from_hash(h).to_payload; end
|
|
132
|
+
|
|
133
|
+
# parse json representation (see also #to_json)
|
|
134
|
+
def self.from_json(json_string); from_hash( JSON.load(json_string) ); end
|
|
135
|
+
|
|
136
|
+
# convert json representation to raw binary
|
|
137
|
+
def self.binary_from_json(json_string); from_json(json_string).to_payload; end
|
|
138
|
+
|
|
139
|
+
# convert header to json representation.
|
|
140
|
+
def header_to_json(options = {:space => ''})
|
|
141
|
+
h = to_hash
|
|
142
|
+
%w[tx mrkl_tree].each{|k| h.delete(k) }
|
|
143
|
+
JSON.pretty_generate( h, options )
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# read binary block from a file
|
|
147
|
+
def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
|
|
148
|
+
|
|
149
|
+
# read json block from a file
|
|
150
|
+
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Handler
|
|
5
|
+
def on_inv_transaction(hash)
|
|
6
|
+
p ['inv transaction', hth(hash)]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def on_inv_block(hash)
|
|
10
|
+
p ['inv block', hth(hash)]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def on_get_transaction(hash)
|
|
14
|
+
p ['get transaction', hth(hash)]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def on_get_block(hash)
|
|
18
|
+
p ['get block', hth(hash)]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_addr(addr)
|
|
22
|
+
p ['addr', addr, addr.alive?]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_tx(tx)
|
|
26
|
+
p ['tx', tx]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def on_block(block)
|
|
30
|
+
#p ['block', block]
|
|
31
|
+
puts block.to_json
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hth(h); h.unpack("H*")[0]; end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Parser
|
|
5
|
+
|
|
6
|
+
def initialize(handler=nil)
|
|
7
|
+
@h = handler || Handler.new
|
|
8
|
+
@buf = ""
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def log
|
|
12
|
+
@log ||= Bitcoin::Logger.create("parser")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# handles inv/getdata packets
|
|
16
|
+
#
|
|
17
|
+
def parse_inv(payload, type=:put)
|
|
18
|
+
count, payload = Protocol.unpack_var_int(payload)
|
|
19
|
+
payload.each_byte.each_slice(36){|i|
|
|
20
|
+
hash = i[4..-1].reverse.pack("C32")
|
|
21
|
+
case i[0]
|
|
22
|
+
when 1
|
|
23
|
+
if type == :put
|
|
24
|
+
@h.on_inv_transaction(hash)
|
|
25
|
+
else
|
|
26
|
+
@h.on_get_transaction(hash)
|
|
27
|
+
end
|
|
28
|
+
when 2
|
|
29
|
+
if type == :put
|
|
30
|
+
@h.on_inv_block(hash)
|
|
31
|
+
else
|
|
32
|
+
@h.on_get_block(hash)
|
|
33
|
+
end
|
|
34
|
+
else
|
|
35
|
+
p ['parse_inv error', i]
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def hth(h); h.unpack("H*")[0]; end
|
|
41
|
+
|
|
42
|
+
def parse_addr(payload)
|
|
43
|
+
count, payload = Protocol.unpack_var_int(payload)
|
|
44
|
+
payload.each_byte.each_slice(30){|i|
|
|
45
|
+
begin
|
|
46
|
+
addr = Addr.new(i.pack("C*"))
|
|
47
|
+
rescue
|
|
48
|
+
puts "Error parsing addr: #{i.inspect}"
|
|
49
|
+
end
|
|
50
|
+
@h.on_addr( addr )
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_headers(payload)
|
|
55
|
+
count, payload = Protocol.unpack_var_int(payload)
|
|
56
|
+
idx = 0
|
|
57
|
+
headers = count.times.map{ Block.new(payload[idx..idx+=81]) }
|
|
58
|
+
@h.on_headers(headers)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse_getblocks(payload)
|
|
62
|
+
version, payload = payload.unpack('a4a*')
|
|
63
|
+
count, payload = Protocol.unpack_var_int(payload)
|
|
64
|
+
buf, payload = payload.unpack("a#{count*32}a*")
|
|
65
|
+
hashes = buf.each_byte.each_slice(32).map{|i| hash = Protocol.hth(i.reverse.pack("C32")) }
|
|
66
|
+
stop_hash = Protocol.hth(payload[0..32].reverse)
|
|
67
|
+
[version, hashes, stop_hash]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process_pkt(command, payload)
|
|
71
|
+
case command
|
|
72
|
+
when 'tx'; @h.on_tx( Tx.new(payload) )
|
|
73
|
+
when 'block'; @h.on_block( Block.new(payload) )
|
|
74
|
+
when 'headers'; parse_headers(payload)
|
|
75
|
+
when 'inv'; parse_inv(payload, :put)
|
|
76
|
+
when 'getdata'; parse_inv(payload, :get)
|
|
77
|
+
when 'addr'; parse_addr(payload)
|
|
78
|
+
when 'verack'; @h.on_handshake_complete # nop
|
|
79
|
+
when 'version'; parse_version(payload)
|
|
80
|
+
when 'alert'; parse_alert(payload)
|
|
81
|
+
when 'ping'; @h.on_ping(payload.unpack("Q")[0])
|
|
82
|
+
when 'pong'; @h.on_pong(payload.unpack("Q")[0])
|
|
83
|
+
when 'getblocks'; @h.on_getblocks(*parse_getblocks(payload))
|
|
84
|
+
when 'getheaders'; @h.on_getheaders(*parse_getblocks(payload))
|
|
85
|
+
else
|
|
86
|
+
p ['unkown-packet', command, payload]
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_version(payload)
|
|
91
|
+
version = Bitcoin::Protocol::Version.parse(payload)
|
|
92
|
+
@h.on_version(version)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def parse_alert(payload)
|
|
96
|
+
return unless @h.respond_to?(:on_alert)
|
|
97
|
+
@h.on_alert Bitcoin::Protocol::Alert.parse(payload)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def parse(buf)
|
|
101
|
+
@buf += buf
|
|
102
|
+
while parse_buffer; end
|
|
103
|
+
@buf
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def parse_buffer
|
|
107
|
+
head_magic = Bitcoin::network[:magic_head]
|
|
108
|
+
head_size = 24
|
|
109
|
+
return false if @buf.size <= head_size
|
|
110
|
+
|
|
111
|
+
magic, cmd, length, checksum = @buf.unpack("a4A12Ia4")
|
|
112
|
+
payload = @buf[head_size...head_size+length]
|
|
113
|
+
|
|
114
|
+
unless magic == head_magic
|
|
115
|
+
handle_error(:close, "head_magic not found")
|
|
116
|
+
@buf = ''
|
|
117
|
+
else
|
|
118
|
+
|
|
119
|
+
if Digest::SHA256.digest(Digest::SHA256.digest( payload ))[0...4] != checksum
|
|
120
|
+
if (length < 50000) && (payload.size < length)
|
|
121
|
+
size_info = [payload.size, length].join('/')
|
|
122
|
+
handle_error(:debug, "chunked packet stream (#{size_info})")
|
|
123
|
+
else
|
|
124
|
+
handle_error(:close, "checksum mismatch")
|
|
125
|
+
end
|
|
126
|
+
return
|
|
127
|
+
end
|
|
128
|
+
@buf = @buf[head_size+length..-1] || ""
|
|
129
|
+
|
|
130
|
+
process_pkt(cmd, payload)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# not empty yet? parse more.
|
|
134
|
+
@buf[0] != nil
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def handle_error(type, msg)
|
|
138
|
+
case type
|
|
139
|
+
when :close
|
|
140
|
+
log.debug {"closing packet stream (#{msg})"}
|
|
141
|
+
else
|
|
142
|
+
log.debug { [type, msg] }
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end # Parser
|
|
146
|
+
|
|
147
|
+
end
|
|
148
|
+
end
|