bitcoin-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (140) hide show
  1. data/.gitignore +12 -0
  2. data/COPYING +18 -0
  3. data/Gemfile +4 -0
  4. data/README.rdoc +189 -0
  5. data/Rakefile +104 -0
  6. data/bin/bitcoin_dns_seed +130 -0
  7. data/bin/bitcoin_gui +80 -0
  8. data/bin/bitcoin_node +174 -0
  9. data/bin/bitcoin_shell +12 -0
  10. data/bin/bitcoin_wallet +323 -0
  11. data/bitcoin-ruby.gemspec +27 -0
  12. data/concept-examples/blockchain-pow.rb +151 -0
  13. data/doc/CONFIG.rdoc +66 -0
  14. data/doc/EXAMPLES.rdoc +9 -0
  15. data/doc/NODE.rdoc +35 -0
  16. data/doc/STORAGE.rdoc +21 -0
  17. data/doc/WALLET.rdoc +102 -0
  18. data/examples/balance.rb +60 -0
  19. data/examples/bbe_verify_tx.rb +55 -0
  20. data/examples/connect.rb +36 -0
  21. data/examples/relay_tx.rb +22 -0
  22. data/examples/verify_tx.rb +57 -0
  23. data/lib/bitcoin.rb +370 -0
  24. data/lib/bitcoin/builder.rb +266 -0
  25. data/lib/bitcoin/config.rb +56 -0
  26. data/lib/bitcoin/connection.rb +126 -0
  27. data/lib/bitcoin/ffi/openssl.rb +121 -0
  28. data/lib/bitcoin/gui/addr_view.rb +42 -0
  29. data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
  30. data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
  31. data/lib/bitcoin/gui/conn_view.rb +36 -0
  32. data/lib/bitcoin/gui/connection.rb +68 -0
  33. data/lib/bitcoin/gui/em_gtk.rb +28 -0
  34. data/lib/bitcoin/gui/gui.builder +1643 -0
  35. data/lib/bitcoin/gui/gui.rb +290 -0
  36. data/lib/bitcoin/gui/helpers.rb +113 -0
  37. data/lib/bitcoin/gui/tree_view.rb +82 -0
  38. data/lib/bitcoin/gui/tx_view.rb +67 -0
  39. data/lib/bitcoin/key.rb +125 -0
  40. data/lib/bitcoin/logger.rb +65 -0
  41. data/lib/bitcoin/network/command_client.rb +93 -0
  42. data/lib/bitcoin/network/command_handler.rb +179 -0
  43. data/lib/bitcoin/network/connection_handler.rb +274 -0
  44. data/lib/bitcoin/network/node.rb +399 -0
  45. data/lib/bitcoin/protocol.rb +140 -0
  46. data/lib/bitcoin/protocol/address.rb +48 -0
  47. data/lib/bitcoin/protocol/alert.rb +47 -0
  48. data/lib/bitcoin/protocol/block.rb +154 -0
  49. data/lib/bitcoin/protocol/handler.rb +38 -0
  50. data/lib/bitcoin/protocol/parser.rb +148 -0
  51. data/lib/bitcoin/protocol/tx.rb +205 -0
  52. data/lib/bitcoin/protocol/txin.rb +97 -0
  53. data/lib/bitcoin/protocol/txout.rb +73 -0
  54. data/lib/bitcoin/protocol/version.rb +70 -0
  55. data/lib/bitcoin/script.rb +634 -0
  56. data/lib/bitcoin/storage/dummy.rb +164 -0
  57. data/lib/bitcoin/storage/models.rb +133 -0
  58. data/lib/bitcoin/storage/sequel.rb +335 -0
  59. data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
  60. data/lib/bitcoin/storage/storage.rb +243 -0
  61. data/lib/bitcoin/version.rb +3 -0
  62. data/lib/bitcoin/wallet/coinselector.rb +30 -0
  63. data/lib/bitcoin/wallet/keygenerator.rb +75 -0
  64. data/lib/bitcoin/wallet/keystore.rb +203 -0
  65. data/lib/bitcoin/wallet/txdp.rb +116 -0
  66. data/lib/bitcoin/wallet/wallet.rb +243 -0
  67. data/spec/bitcoin/bitcoin_spec.rb +472 -0
  68. data/spec/bitcoin/builder_spec.rb +90 -0
  69. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
  70. data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
  71. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
  72. data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
  73. data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
  74. data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
  75. data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
  76. data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
  77. data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
  78. data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
  79. data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
  80. data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
  81. data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
  82. data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
  83. data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
  84. data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
  85. data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
  86. data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
  87. data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
  88. data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
  89. data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
  90. data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
  91. data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
  92. data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
  93. data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
  94. data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
  95. data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
  96. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
  97. data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
  98. data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
  99. data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
  100. data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
  101. data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
  102. data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
  103. data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
  104. data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
  105. data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
  106. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
  107. data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
  108. data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
  109. data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
  110. data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
  111. data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
  112. data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
  113. data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
  114. data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
  115. data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
  116. data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
  117. data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
  118. data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
  119. data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
  120. data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
  121. data/spec/bitcoin/key_spec.rb +123 -0
  122. data/spec/bitcoin/network_spec.rb +48 -0
  123. data/spec/bitcoin/protocol/addr_spec.rb +68 -0
  124. data/spec/bitcoin/protocol/alert_spec.rb +20 -0
  125. data/spec/bitcoin/protocol/block_spec.rb +101 -0
  126. data/spec/bitcoin/protocol/inv_spec.rb +124 -0
  127. data/spec/bitcoin/protocol/ping_spec.rb +49 -0
  128. data/spec/bitcoin/protocol/tx_spec.rb +226 -0
  129. data/spec/bitcoin/protocol/version_spec.rb +77 -0
  130. data/spec/bitcoin/reorg_spec.rb +129 -0
  131. data/spec/bitcoin/script/opcodes_spec.rb +417 -0
  132. data/spec/bitcoin/script/script_spec.rb +246 -0
  133. data/spec/bitcoin/spec_helper.rb +36 -0
  134. data/spec/bitcoin/storage_spec.rb +229 -0
  135. data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
  136. data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
  137. data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
  138. data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
  139. data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
  140. 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