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
@@ -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
|