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,164 @@
1
+ module Bitcoin::Storage::Backends
2
+ class DummyStore < StoreBase
3
+
4
+ attr_accessor :blk, :tx
5
+
6
+ def initialize *args
7
+ reset
8
+ super(*args)
9
+ end
10
+
11
+ def reset
12
+ @blk, @tx = [], {}
13
+ end
14
+
15
+ def store_block(blk)
16
+ return false unless blk
17
+ if block = get_block(blk.hash)
18
+ log.info { "Block already stored; skipping" }
19
+ return false
20
+ end
21
+
22
+ prev_block = get_block(Bitcoin::hth(blk.prev_block.reverse))
23
+ unless prev_block
24
+ unless blk.hash == Bitcoin.network[:genesis_hash]
25
+ log.warn { "INVALID BLOCK: #{blk.hash}" }
26
+ return false
27
+ end
28
+ end
29
+
30
+ blk.tx.each {|tx| store_tx(tx) }
31
+ @blk << blk
32
+
33
+ log.info { "NEW HEAD: #{blk.hash} DEPTH: #{get_depth}" }
34
+ get_depth
35
+ end
36
+
37
+ def store_tx(tx)
38
+ if @tx.keys.include?(tx.hash)
39
+ log.info { "Tx already stored; skipping" }
40
+ return tx
41
+ end
42
+ @tx[tx.hash] = tx
43
+ end
44
+
45
+ def has_block(blk_hash)
46
+ !!get_block(blk_hash)
47
+ end
48
+
49
+ def has_tx(tx_hash)
50
+ !!get_tx(tx_hash)
51
+ end
52
+
53
+ def get_depth
54
+ @blk.size - 1
55
+ end
56
+
57
+ def get_head
58
+ wrap_block(@blk[-1])
59
+ end
60
+
61
+ def get_block_by_depth(depth)
62
+ wrap_block(@blk[depth])
63
+ end
64
+
65
+ def get_block_by_prev_hash(hash)
66
+ wrap_block(@blk.find {|blk| blk.prev_block == [hash].pack("H*").reverse})
67
+ end
68
+
69
+ def get_block(blk_hash)
70
+ wrap_block(@blk.find {|blk| blk.hash == blk_hash})
71
+ end
72
+
73
+ def get_block_by_id(blk_id)
74
+ wrap_block(@blk[blk_id])
75
+ end
76
+
77
+ def get_block_by_tx(tx_hash)
78
+ wrap_block(@blk.find {|blk| blk.tx.map(&:hash).include?(tx_hash) })
79
+ end
80
+
81
+ def get_tx(tx_hash)
82
+ transaction = @tx[tx_hash]
83
+ return nil unless transaction
84
+ wrap_tx(transaction)
85
+ end
86
+
87
+ def get_tx_by_id(tx_id)
88
+ wrap_tx(@tx[tx_id])
89
+ end
90
+
91
+ def get_txin_for_txout(tx_hash, txout_idx)
92
+ txin = @tx.values.map(&:in).flatten.find {|i| i.prev_out_index == txout_idx &&
93
+ i.prev_out == [tx_hash].pack("H*").reverse }
94
+ wrap_txin(txin)
95
+ end
96
+
97
+ def get_txouts_for_pk_script(script)
98
+ txouts = @tx.values.map(&:out).flatten.select {|o| o.pk_script == script}
99
+ txouts.map {|o| wrap_txout(o) }
100
+ end
101
+
102
+ def get_txouts_for_hash160(hash160)
103
+ @tx.values.map(&:out).flatten.map {|o|
104
+ o = wrap_txout(o)
105
+ o.hash160 == hash160 ? o : nil
106
+ }.compact
107
+ end
108
+
109
+ def wrap_block(block)
110
+ return nil unless block
111
+ data = {:id => @blk.index(block), :depth => @blk.index(block)}
112
+ blk = Bitcoin::Storage::Models::Block.new(self, data)
113
+ [:ver, :prev_block, :mrkl_root, :time, :bits, :nonce].each do |attr|
114
+ blk.send("#{attr}=", block.send(attr))
115
+ end
116
+ block.tx.each do |tx|
117
+ blk.tx << get_tx(tx.hash)
118
+ end
119
+ blk.recalc_block_hash
120
+ blk
121
+ end
122
+
123
+ def wrap_tx(transaction)
124
+ return nil unless transaction
125
+ blk = @blk.find{|b| b.tx.include?(transaction)}
126
+ data = {:id => transaction.hash, :blk_id => @blk.index(blk)}
127
+ tx = Bitcoin::Storage::Models::Tx.new(self, data)
128
+ tx.ver = transaction.ver
129
+ tx.lock_time = transaction.lock_time
130
+ transaction.in.each {|i| tx.add_in(wrap_txin(i))}
131
+ transaction.out.each {|o| tx.add_out(wrap_txout(o))}
132
+ tx.hash = tx.hash_from_payload(tx.to_payload)
133
+ tx
134
+ end
135
+
136
+ def wrap_txin(input)
137
+ return nil unless input
138
+ tx = @tx.values.find{|t| t.in.include?(input)}
139
+ data = {:tx_id => tx.hash, :tx_idx => tx.in.index(input)}
140
+ txin = Bitcoin::Storage::Models::TxIn.new(self, data)
141
+ [:prev_out, :prev_out_index, :script_sig_length, :script_sig, :sequence].each do |attr|
142
+ txin.send("#{attr}=", input.send(attr))
143
+ end
144
+ txin
145
+ end
146
+
147
+ def wrap_txout(output)
148
+ return nil unless output
149
+ tx = @tx.values.find{|t| t.out.include?(output)}
150
+ data = {:tx_id => tx.hash, :tx_idx => tx.out.index(output),
151
+ :hash160 => Bitcoin::Script.new(output.pk_script).get_hash160 }
152
+ txout = Bitcoin::Storage::Models::TxOut.new(self, data)
153
+ [:value, :pk_script_length, :pk_script].each do |attr|
154
+ txout.send("#{attr}=", output.send(attr))
155
+ end
156
+ txout
157
+ end
158
+
159
+ def to_s
160
+ "DummyStore"
161
+ end
162
+
163
+ end
164
+ end
@@ -0,0 +1,133 @@
1
+ # StorageModels defines objects that are returned from storage.
2
+ # These objects inherit from their Bitcoin::Protocol counterpart
3
+ # and add some additional data and methods.
4
+ #
5
+ # * Bitcoin::Storage::Models::Block
6
+ # * Bitcoin::Storage::Models::Tx
7
+ # * Bitcoin::Storage::Models::TxIn
8
+ # * Bitcoin::Storage::Models::TxOut
9
+ module Bitcoin::Storage::Models
10
+
11
+ # Block retrieved from storage. (see Bitcoin::Protocol::Block)
12
+ class Block < Bitcoin::Protocol::Block
13
+
14
+ attr_accessor :ver, :prev_block, :mrkl_root, :time, :bits, :nonce, :tx
15
+ attr_reader :store, :id, :depth, :chain
16
+
17
+ def initialize store, data
18
+ @store = store
19
+ @id = data[:id]
20
+ @depth = data[:depth]
21
+ @chain = data[:chain]
22
+ @tx = []
23
+ end
24
+
25
+ # get the block this one builds upon
26
+ def get_prev_block
27
+ @store.get_block(hth(@prev_block))
28
+ end
29
+
30
+ # get the block that builds upon this one
31
+ def get_next_block
32
+ @store.get_block_by_prev_hash(@hash)
33
+ end
34
+
35
+ end
36
+
37
+ # Transaction retrieved from storage. (see Bitcoin::Protocol::Tx)
38
+ class Tx < Bitcoin::Protocol::Tx
39
+
40
+ attr_accessor :ver, :lock_time, :hash
41
+ attr_reader :store, :id, :blk_id
42
+
43
+ def initialize store, data
44
+ @store = store
45
+ @id = data[:id]
46
+ @blk_id = data[:blk_id]
47
+ super(nil)
48
+ end
49
+
50
+ # get the block this transaction is in
51
+ def get_block
52
+ return nil unless @blk_id
53
+ @store.get_block_by_id(@blk_id)
54
+ end
55
+
56
+ # get the number of blocks that confirm this tx in the main chain
57
+ def confirmations
58
+ return 0 unless get_block
59
+ @store.get_head.depth - get_block.depth + 1
60
+ end
61
+ end
62
+
63
+ # Transaction input retrieved from storage. (see Bitcoin::Protocol::TxIn
64
+ class TxIn < Bitcoin::Protocol::TxIn
65
+
66
+ attr_reader :store, :id, :tx_id, :tx_idx
67
+
68
+ def initialize store, data
69
+ @store = store
70
+ @id = data[:id]
71
+ @tx_id = data[:tx_id]
72
+ @tx_idx = data[:tx_idx]
73
+ end
74
+
75
+ # get the transaction this input is in
76
+ def get_tx
77
+
78
+ @store.get_tx_by_id(@tx_id)
79
+ end
80
+
81
+ # get the previous output referenced by this input
82
+ def get_prev_out
83
+ prev_tx = @store.get_tx(@prev_out.reverse.unpack("H*")[0])
84
+ return nil unless prev_tx
85
+ prev_tx.out[@prev_out_index]
86
+ end
87
+
88
+ end
89
+
90
+ # Transaction output retrieved from storage. (see Bitcoin::Protocol::TxOut)
91
+ class TxOut < Bitcoin::Protocol::TxOut
92
+
93
+ attr_reader :store, :id, :tx_id, :tx_idx, :type
94
+
95
+ def initialize store, data
96
+ @store = store
97
+ @id = data[:id]
98
+ @tx_id = data[:tx_id]
99
+ @tx_idx = data[:tx_idx]
100
+ @type = data[:type]
101
+ end
102
+
103
+ def hash160
104
+ Bitcoin::Script.new(@pk_script).get_hash160
105
+ end
106
+
107
+ # get the transaction this output is in
108
+ def get_tx
109
+ @store.get_tx_by_id(@tx_id)
110
+ end
111
+
112
+ # get the next input that references this output
113
+ def get_next_in
114
+ @store.get_txin_for_txout(get_tx.hash, @tx_idx)
115
+ end
116
+
117
+ # get all addresses this txout corresponds to (if possible)
118
+ def get_address
119
+ Bitcoin::Script.new(@pk_script).get_address
120
+ end
121
+
122
+ # get the single address this txout corresponds to (first for multisig tx)
123
+ def get_addresses
124
+ Bitcoin::Script.new(@pk_script).get_addresses
125
+ end
126
+
127
+ def type
128
+ Bitcoin::Script.new(@pk_script).type
129
+ end
130
+
131
+ end
132
+
133
+ end
@@ -0,0 +1,335 @@
1
+ Bitcoin.require_dependency :sequel, message:
2
+ "Note: You will also need an adapter for your database like sqlite3, mysql2, postgresql"
3
+ require 'bitcoin/storage/sequel_store/sequel_migrations'
4
+
5
+ module Bitcoin::Storage::Backends
6
+
7
+ # Storage backend using Sequel to connect to arbitrary SQL databases.
8
+ # Inherits from StoreBase and implements its interface.
9
+ class SequelStore < StoreBase
10
+
11
+
12
+ # possible script types
13
+ SCRIPT_TYPES = [:unknown, :pubkey, :hash160, :multisig, :p2sh]
14
+
15
+ # sequel database connection
16
+ attr_accessor :db
17
+
18
+ include Bitcoin::Storage::Backends::SequelMigrations
19
+
20
+ # create sequel store with given +config+
21
+ def initialize config, *args
22
+ @config = config
23
+ connect
24
+ super config, *args
25
+ end
26
+
27
+ # connect to database
28
+ def connect
29
+ {:sqlite => "sqlite3", :postgres => "pg", :mysql => "mysql",
30
+ }.each do |adapter, name|
31
+ if @config[:db].split(":").first == adapter.to_s
32
+ Bitcoin.require_dependency name, gem: name
33
+ end
34
+ end
35
+
36
+ @db = Sequel.connect(@config[:db])
37
+ migrate
38
+ end
39
+
40
+ # reset database; delete all data
41
+ def reset
42
+ [:blk, :blk_tx, :tx, :txin, :txout].each {|table| @db[table].delete}
43
+ end
44
+
45
+ # persist given block +blk+ to storage.
46
+ def persist_block blk, chain, depth
47
+ @db.transaction do
48
+ attrs = {
49
+ :hash => htb(blk.hash).to_sequel_blob,
50
+ :depth => depth,
51
+ :chain => chain,
52
+ :version => blk.ver,
53
+ :prev_hash => blk.prev_block.reverse.to_sequel_blob,
54
+ :mrkl_root => blk.mrkl_root.reverse.to_sequel_blob,
55
+ :time => blk.time,
56
+ :bits => blk.bits,
57
+ :nonce => blk.nonce,
58
+ :blk_size => blk.to_payload.bytesize,
59
+ }
60
+ existing = @db[:blk].filter(:hash => htb(blk.hash).to_sequel_blob)
61
+ if existing.any?
62
+ existing.update attrs
63
+ else
64
+ block_id = @db[:blk].insert(attrs)
65
+ blk.tx.each_with_index do |tx, idx|
66
+ tx_id = store_tx(tx)
67
+ raise "Error saving tx #{tx.hash} in block #{blk.hash}" unless tx_id
68
+ @db[:blk_tx].insert({
69
+ :blk_id => block_id,
70
+ :tx_id => tx_id,
71
+ :idx => idx,
72
+ })
73
+ end
74
+ end
75
+ begin
76
+ @db[:blk].where(:prev_hash => htb(blk.hash).to_sequel_blob, :chain => ORPHAN).each do |b|
77
+ log.debug { "re-org orphan #{hth(b[:hash])}" }
78
+ begin
79
+ store_block(get_block(hth(b[:hash])))
80
+ rescue SystemStackError
81
+ EM.defer { store_block(get_block(hth(b[:hash]))) } if EM.reactor_running?
82
+ end
83
+ end
84
+ rescue
85
+ p $!
86
+ end
87
+ log.info { "block #{blk.hash} (#{depth}, #{['main', 'side', 'orphan'][chain]})" }
88
+ return depth, chain
89
+ end
90
+ end
91
+
92
+ # update +attrs+ for block with given +hash+.
93
+ def update_blocks updates
94
+ @db.transaction do
95
+ updates.each do |blocks, attrs|
96
+ @db[:blk].filter(:hash => blocks.map{|h| htb(h).to_sequel_blob}).update(attrs)
97
+ end
98
+ end
99
+ end
100
+
101
+ # store transaction +tx+
102
+ def store_tx(tx)
103
+ @log.debug { "Storing tx #{tx.hash} (#{tx.to_payload.bytesize} bytes)" }
104
+ @db.transaction do
105
+ transaction = @db[:tx][:hash => htb(tx.hash).to_sequel_blob]
106
+ return transaction[:id] if transaction
107
+ tx_id = @db[:tx].insert({
108
+ :hash => htb(tx.hash).to_sequel_blob,
109
+ :version => tx.ver,
110
+ :lock_time => tx.lock_time,
111
+ :coinbase => tx.in.size==1 && tx.in[0].coinbase?,
112
+ :tx_size => tx.payload.bytesize,
113
+ })
114
+ tx.in.each_with_index {|i, idx| store_txin(tx_id, i, idx)}
115
+ tx.out.each_with_index {|o, idx| store_txout(tx_id, o, idx)}
116
+ tx_id
117
+ end
118
+ rescue
119
+ @log.warn { "Error storing tx: #{$!}" }
120
+ end
121
+
122
+ # store input +txin+
123
+ def store_txin(tx_id, txin, idx)
124
+ @db[:txin].insert({
125
+ :tx_id => tx_id,
126
+ :tx_idx => idx,
127
+ :script_sig => txin.script_sig.to_sequel_blob,
128
+ :prev_out => txin.prev_out.to_sequel_blob,
129
+ :prev_out_index => txin.prev_out_index,
130
+ :sequence => txin.sequence.unpack("I")[0],
131
+ })
132
+ end
133
+
134
+ # store output +txout+
135
+ def store_txout(tx_id, txout, idx)
136
+ script = Bitcoin::Script.new(txout.pk_script)
137
+ txout_id = @db[:txout].insert({
138
+ :tx_id => tx_id,
139
+ :tx_idx => idx,
140
+ :pk_script => txout.pk_script.to_sequel_blob,
141
+ :value => txout.value,
142
+ :type => SCRIPT_TYPES.index(script.type)
143
+ })
144
+ if script.is_hash160? || script.is_pubkey?
145
+ store_addr(txout_id, script.get_hash160)
146
+ elsif script.is_multisig?
147
+ script.get_multisig_pubkeys.map do |pubkey|
148
+ store_addr(txout_id, Bitcoin.hash160(pubkey.unpack("H*")[0]))
149
+ end
150
+ end
151
+ txout_id
152
+ end
153
+
154
+ # store address +hash160+
155
+ def store_addr(txout_id, hash160)
156
+ addr = @db[:addr][:hash160 => hash160]
157
+ addr_id = addr[:id] if addr
158
+ addr_id ||= @db[:addr].insert({:hash160 => hash160})
159
+ @db[:addr_txout].insert({:addr_id => addr_id, :txout_id => txout_id})
160
+ end
161
+
162
+ # check if block +blk_hash+ exists
163
+ def has_block(blk_hash)
164
+ !!@db[:blk].where(:hash => htb(blk_hash).to_sequel_blob).get(1)
165
+ end
166
+
167
+ # check if transaction +tx_hash+ exists
168
+ def has_tx(tx_hash)
169
+ !!@db[:tx].where(:hash => htb(tx_hash).to_sequel_blob).get(1)
170
+ end
171
+
172
+ # get head block (highest block from the MAIN chain)
173
+ def get_head
174
+ wrap_block(@db[:blk].filter(:chain => MAIN).order(:depth).last)
175
+ end
176
+
177
+ # get depth of MAIN chain
178
+ def get_depth
179
+ return -1 unless get_head
180
+ @db[:blk][:hash => htb(get_head.hash).to_sequel_blob][:depth]
181
+ end
182
+
183
+ # get block for given +blk_hash+
184
+ def get_block(blk_hash)
185
+ wrap_block(@db[:blk][:hash => htb(blk_hash).to_sequel_blob])
186
+ end
187
+
188
+ # get block by given +depth+
189
+ def get_block_by_depth(depth)
190
+ wrap_block(@db[:blk][:depth => depth, :chain => MAIN])
191
+ end
192
+
193
+ # get block by given +prev_hash+
194
+ def get_block_by_prev_hash(prev_hash)
195
+ wrap_block(@db[:blk][:prev_hash => htb(prev_hash).to_sequel_blob])
196
+ end
197
+
198
+ # get block by given +tx_hash+
199
+ def get_block_by_tx(tx_hash)
200
+ tx = @db[:tx][:hash => htb(tx_hash).to_sequel_blob]
201
+ return nil unless tx
202
+ parent = @db[:blk_tx][:tx_id => tx[:id]]
203
+ return nil unless parent
204
+ wrap_block(@db[:blk][:id => parent[:blk_id]])
205
+ end
206
+
207
+ # get block by given +id+
208
+ def get_block_by_id(block_id)
209
+ wrap_block(@db[:blk][:id => block_id])
210
+ end
211
+
212
+ # get transaction for given +tx_hash+
213
+ def get_tx(tx_hash)
214
+ wrap_tx(@db[:tx][:hash => htb(tx_hash).to_sequel_blob])
215
+ end
216
+
217
+ # get transaction by given +tx_id+
218
+ def get_tx_by_id(tx_id)
219
+ wrap_tx(@db[:tx][:id => tx_id])
220
+ end
221
+
222
+ # get corresponding Models::TxIn for the txout in transaction
223
+ # +tx_hash+ with index +txout_idx+
224
+ def get_txin_for_txout(tx_hash, txout_idx)
225
+ tx_hash = htb(tx_hash).reverse.to_sequel_blob
226
+ wrap_txin(@db[:txin][:prev_out => tx_hash, :prev_out_index => txout_idx])
227
+ end
228
+
229
+ # get corresponding Models::TxOut for +txin+
230
+ def get_txout_for_txin(txin)
231
+ tx = @db[:tx][:hash => txin.prev_out.reverse.to_sequel_blob]
232
+ return nil unless tx
233
+ wrap_txout(@db[:txout][:tx_idx => txin.prev_out_index, :tx_id => tx[:id]])
234
+ end
235
+
236
+ # get all Models::TxOut matching given +script+
237
+ def get_txouts_for_pk_script(script)
238
+ txouts = @db[:txout].filter(:pk_script => script.to_sequel_blob).order(:id)
239
+ txouts.map{|txout| wrap_txout(txout)}
240
+ end
241
+
242
+ # get all Models::TxOut matching given +hash160+
243
+ def get_txouts_for_hash160(hash160, unconfirmed = false)
244
+ addr = @db[:addr][:hash160 => hash160]
245
+ return [] unless addr
246
+ txouts = @db[:addr_txout].where(:addr_id => addr[:id])
247
+ .map{|t| @db[:txout][:id => t[:txout_id]] }
248
+ .map{|o| wrap_txout(o) }
249
+ unless unconfirmed
250
+ txouts.select!{|o| o.get_tx.get_block.chain == MAIN rescue false }
251
+ end
252
+ txouts
253
+ end
254
+
255
+ # get all unconfirmed Models::TxOut
256
+ def get_unconfirmed_tx
257
+ @db[:unconfirmed].map{|t| wrap_tx(t)}
258
+ end
259
+
260
+ # wrap given +block+ into Models::Block
261
+ def wrap_block(block)
262
+ return nil unless block
263
+
264
+ data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain]}
265
+ blk = Bitcoin::Storage::Models::Block.new(self, data)
266
+
267
+ blk.ver = block[:version]
268
+ blk.prev_block = block[:prev_hash].reverse
269
+ blk.mrkl_root = block[:mrkl_root].reverse
270
+ blk.time = block[:time].to_i
271
+ blk.bits = block[:bits]
272
+ blk.nonce = block[:nonce]
273
+ parents = db[:blk_tx].filter(:blk_id => block[:id])
274
+ .order(:idx) rescue []
275
+ parents.each do |parent|
276
+ transaction = db[:tx][:id => parent[:tx_id]]
277
+ blk.tx << wrap_tx(transaction)
278
+ end
279
+
280
+ blk.recalc_block_hash
281
+ blk
282
+ end
283
+
284
+ # wrap given +transaction+ into Models::Transaction
285
+ def wrap_tx(transaction)
286
+ return nil unless transaction
287
+
288
+ parents = @db[:blk_tx].where(:tx_id => transaction[:id])
289
+ parent = parents.map{|m|@db[:blk][:id => m[:blk_id]]}.sort_by {|b| b[:chain]}.first
290
+ block_id = parent ? parent[:id] : nil
291
+ data = {:id => transaction[:id], :blk_id => block_id}
292
+ tx = Bitcoin::Storage::Models::Tx.new(self, data)
293
+
294
+ inputs = db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
295
+ inputs.each { |i| tx.add_in(wrap_txin(i)) }
296
+
297
+ outputs = db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
298
+ outputs.each { |o| tx.add_out(wrap_txout(o)) }
299
+ tx.ver = transaction[:version]
300
+ tx.lock_time = transaction[:lock_time]
301
+ tx.hash = tx.hash_from_payload(tx.to_payload)
302
+ tx
303
+ end
304
+
305
+ # wrap given +input+ into Models::TxIn
306
+ def wrap_txin(input)
307
+ return nil unless input
308
+ data = {:id => input[:id], :tx_id => input[:tx_id], :tx_idx => input[:tx_idx]}
309
+ txin = Bitcoin::Storage::Models::TxIn.new(self, data)
310
+ txin.prev_out = input[:prev_out]
311
+ txin.prev_out_index = input[:prev_out_index]
312
+ txin.script_sig_length = input[:script_sig].bytesize
313
+ txin.script_sig = input[:script_sig]
314
+ txin.sequence = [input[:sequence]].pack("I")
315
+ txin
316
+ end
317
+
318
+ # wrap given +output+ into Models::TxOut
319
+ def wrap_txout(output)
320
+ return nil unless output
321
+ data = {:id => output[:id], :tx_id => output[:tx_id], :tx_idx => output[:tx_idx],
322
+ :hash160 => output[:hash160], :type => SCRIPT_TYPES[output[:type]]}
323
+ txout = Bitcoin::Storage::Models::TxOut.new(self, data)
324
+ txout.value = output[:value]
325
+ txout.pk_script = output[:pk_script]
326
+ txout
327
+ end
328
+
329
+
330
+ def hth(bin); bin.unpack("H*")[0]; end
331
+ def htb(hex); [hex].pack("H*"); end
332
+
333
+ end
334
+
335
+ end