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.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
|
@@ -0,0 +1,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
|