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