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