bitcoin-ruby 0.0.1 → 0.0.2
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 +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin::Storage::Backends
|
|
2
4
|
class DummyStore < StoreBase
|
|
3
5
|
|
|
@@ -12,29 +14,21 @@ module Bitcoin::Storage::Backends
|
|
|
12
14
|
@blk, @tx = [], {}
|
|
13
15
|
end
|
|
14
16
|
|
|
15
|
-
def
|
|
16
|
-
return
|
|
17
|
+
def persist_block(blk, chain, depth, prev_work = 0)
|
|
18
|
+
return [depth, chain] unless blk && chain == 0
|
|
17
19
|
if block = get_block(blk.hash)
|
|
18
20
|
log.info { "Block already stored; skipping" }
|
|
19
21
|
return false
|
|
20
22
|
end
|
|
21
23
|
|
|
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
24
|
blk.tx.each {|tx| store_tx(tx) }
|
|
31
25
|
@blk << blk
|
|
32
26
|
|
|
33
27
|
log.info { "NEW HEAD: #{blk.hash} DEPTH: #{get_depth}" }
|
|
34
|
-
|
|
28
|
+
[depth, chain]
|
|
35
29
|
end
|
|
36
30
|
|
|
37
|
-
def store_tx(tx)
|
|
31
|
+
def store_tx(tx, validate = true)
|
|
38
32
|
if @tx.keys.include?(tx.hash)
|
|
39
33
|
log.info { "Tx already stored; skipping" }
|
|
40
34
|
return tx
|
|
@@ -99,7 +93,7 @@ module Bitcoin::Storage::Backends
|
|
|
99
93
|
txouts.map {|o| wrap_txout(o) }
|
|
100
94
|
end
|
|
101
95
|
|
|
102
|
-
def get_txouts_for_hash160(hash160)
|
|
96
|
+
def get_txouts_for_hash160(hash160, unconfirmed = false)
|
|
103
97
|
@tx.values.map(&:out).flatten.map {|o|
|
|
104
98
|
o = wrap_txout(o)
|
|
105
99
|
o.hash160 == hash160 ? o : nil
|
|
@@ -108,7 +102,7 @@ module Bitcoin::Storage::Backends
|
|
|
108
102
|
|
|
109
103
|
def wrap_block(block)
|
|
110
104
|
return nil unless block
|
|
111
|
-
data = {:id => @blk.index(block), :depth => @blk.index(block)}
|
|
105
|
+
data = {:id => @blk.index(block), :depth => @blk.index(block), :work => @blk.index(block), :chain => 0}
|
|
112
106
|
blk = Bitcoin::Storage::Models::Block.new(self, data)
|
|
113
107
|
[:ver, :prev_block, :mrkl_root, :time, :bits, :nonce].each do |attr|
|
|
114
108
|
blk.send("#{attr}=", block.send(attr))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
# StorageModels defines objects that are returned from storage.
|
|
2
4
|
# These objects inherit from their Bitcoin::Protocol counterpart
|
|
3
5
|
# and add some additional data and methods.
|
|
@@ -12,19 +14,20 @@ module Bitcoin::Storage::Models
|
|
|
12
14
|
class Block < Bitcoin::Protocol::Block
|
|
13
15
|
|
|
14
16
|
attr_accessor :ver, :prev_block, :mrkl_root, :time, :bits, :nonce, :tx
|
|
15
|
-
attr_reader :store, :id, :depth, :chain
|
|
17
|
+
attr_reader :store, :id, :depth, :chain, :work
|
|
16
18
|
|
|
17
19
|
def initialize store, data
|
|
18
20
|
@store = store
|
|
19
21
|
@id = data[:id]
|
|
20
22
|
@depth = data[:depth]
|
|
21
23
|
@chain = data[:chain]
|
|
24
|
+
@work = data[:work]
|
|
22
25
|
@tx = []
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# get the block this one builds upon
|
|
26
29
|
def get_prev_block
|
|
27
|
-
@store.get_block(
|
|
30
|
+
@store.get_block(@prev_block.reverse_hth)
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
# get the block that builds upon this one
|
|
@@ -80,7 +83,7 @@ module Bitcoin::Storage::Models
|
|
|
80
83
|
|
|
81
84
|
# get the previous output referenced by this input
|
|
82
85
|
def get_prev_out
|
|
83
|
-
prev_tx = @store.get_tx(@prev_out.
|
|
86
|
+
prev_tx = @store.get_tx(@prev_out.reverse_hth)
|
|
84
87
|
return nil unless prev_tx
|
|
85
88
|
prev_tx.out[@prev_out_index]
|
|
86
89
|
end
|
|
@@ -101,7 +104,7 @@ module Bitcoin::Storage::Models
|
|
|
101
104
|
end
|
|
102
105
|
|
|
103
106
|
def hash160
|
|
104
|
-
|
|
107
|
+
script.get_hash160
|
|
105
108
|
end
|
|
106
109
|
|
|
107
110
|
# get the transaction this output is in
|
|
@@ -112,20 +115,30 @@ module Bitcoin::Storage::Models
|
|
|
112
115
|
# get the next input that references this output
|
|
113
116
|
def get_next_in
|
|
114
117
|
@store.get_txin_for_txout(get_tx.hash, @tx_idx)
|
|
118
|
+
rescue
|
|
119
|
+
nil
|
|
115
120
|
end
|
|
116
121
|
|
|
117
122
|
# get all addresses this txout corresponds to (if possible)
|
|
118
123
|
def get_address
|
|
119
|
-
|
|
124
|
+
script.get_address
|
|
120
125
|
end
|
|
121
126
|
|
|
122
127
|
# get the single address this txout corresponds to (first for multisig tx)
|
|
123
128
|
def get_addresses
|
|
124
|
-
|
|
129
|
+
script.get_addresses
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def get_namecoin_name
|
|
133
|
+
@store.get_name_by_txout_id(@id)
|
|
125
134
|
end
|
|
126
135
|
|
|
127
136
|
def type
|
|
128
|
-
|
|
137
|
+
script.type
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def script
|
|
141
|
+
@_script = Bitcoin::Script.new(@pk_script)
|
|
129
142
|
end
|
|
130
143
|
|
|
131
144
|
end
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin::Storage::Backends::SequelMigrations
|
|
2
4
|
|
|
3
5
|
def migrate
|
|
6
|
+
binary = @db.database_type == :postgres ? :bytea : :blob
|
|
7
|
+
|
|
4
8
|
unless @db.tables.include?(:blk)
|
|
5
9
|
@db.create_table :blk do
|
|
6
10
|
primary_key :id
|
|
7
|
-
column :hash,
|
|
11
|
+
column :hash, binary, :null => false, :unique => true, :index => true
|
|
8
12
|
column :depth, :int, :null => false, :index => true
|
|
9
13
|
column :version, :bigint, :null => false
|
|
10
|
-
column :prev_hash,
|
|
11
|
-
column :mrkl_root,
|
|
14
|
+
column :prev_hash, binary, :null => false, :index => true
|
|
15
|
+
column :mrkl_root, binary, :null => false
|
|
12
16
|
column :time, :bigint, :null => false
|
|
13
17
|
column :bits, :bigint, :null => false
|
|
14
18
|
column :nonce, :bigint, :null => false
|
|
15
19
|
column :blk_size, :int, :null => false
|
|
16
20
|
column :chain, :int, :null => false
|
|
21
|
+
column :work, binary, :index => true
|
|
22
|
+
column :aux_pow, binary
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
unless @db.tables.include?(:tx)
|
|
21
27
|
@db.create_table :tx do
|
|
22
28
|
primary_key :id
|
|
23
|
-
column :hash,
|
|
29
|
+
column :hash, binary, :null => false, :unique => true, :index => true
|
|
24
30
|
column :version, :bigint, :null => false
|
|
25
31
|
column :lock_time, :bigint, :null => false
|
|
26
32
|
column :coinbase, :bool, :null => false
|
|
@@ -41,8 +47,8 @@ module Bitcoin::Storage::Backends::SequelMigrations
|
|
|
41
47
|
primary_key :id
|
|
42
48
|
column :tx_id, :int, :null => false, :index => true
|
|
43
49
|
column :tx_idx, :int, :null => false
|
|
44
|
-
column :script_sig,
|
|
45
|
-
column :prev_out,
|
|
50
|
+
column :script_sig, binary, :null => false
|
|
51
|
+
column :prev_out, binary, :null => false, :index => true
|
|
46
52
|
column :prev_out_index, :bigint, :null => false
|
|
47
53
|
column :sequence, :bigint, :null => false
|
|
48
54
|
end
|
|
@@ -53,7 +59,7 @@ module Bitcoin::Storage::Backends::SequelMigrations
|
|
|
53
59
|
primary_key :id
|
|
54
60
|
column :tx_id, :int, :null => false, :index => true
|
|
55
61
|
column :tx_idx, :int, :null => false
|
|
56
|
-
column :pk_script,
|
|
62
|
+
column :pk_script, binary, :null => false
|
|
57
63
|
column :value, :bigint
|
|
58
64
|
column :type, :int, :null => false, :index => true
|
|
59
65
|
end
|
|
@@ -79,6 +85,15 @@ module Bitcoin::Storage::Backends::SequelMigrations
|
|
|
79
85
|
"(SELECT 1 FROM blk_tx WHERE blk_tx.tx_id = tx.id)" +
|
|
80
86
|
"ORDER BY tx.id DESC")
|
|
81
87
|
end
|
|
88
|
+
|
|
89
|
+
unless @db.tables.include?(:names)
|
|
90
|
+
@db.create_table :names do
|
|
91
|
+
column :txout_id, :int, :null => false, :index => true
|
|
92
|
+
column :hash, binary, :index => true
|
|
93
|
+
column :name, binary, :index => true
|
|
94
|
+
column :value, binary
|
|
95
|
+
end
|
|
96
|
+
end
|
|
82
97
|
end
|
|
83
98
|
|
|
84
99
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
up do
|
|
5
|
+
|
|
6
|
+
$stdout.puts "Running migration #{__FILE__}"
|
|
7
|
+
|
|
8
|
+
binary = adapter_scheme == :postgres ? :bytea : :varchar
|
|
9
|
+
|
|
10
|
+
alter_table :schema_info do
|
|
11
|
+
add_column :magic, :varchar # network magic-head
|
|
12
|
+
add_column :backend, :varchar # storage backend
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
next if tables.include?(:blk)
|
|
16
|
+
|
|
17
|
+
create_table :blk do
|
|
18
|
+
primary_key :id
|
|
19
|
+
column :hash, binary, :null => false, :unique => true, :index => true
|
|
20
|
+
column :depth, :int, :null => false, :index => true
|
|
21
|
+
column :version, :bigint, :null => false
|
|
22
|
+
column :prev_hash, binary, :null => false, :index => true
|
|
23
|
+
column :mrkl_root, binary, :null => false
|
|
24
|
+
column :time, :bigint, :null => false
|
|
25
|
+
column :bits, :bigint, :null => false
|
|
26
|
+
column :nonce, :bigint, :null => false
|
|
27
|
+
column :blk_size, :int, :null => false
|
|
28
|
+
column :chain, :int, :null => false
|
|
29
|
+
column :work, binary, :index => true
|
|
30
|
+
column :aux_pow, binary
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
create_table :addr do
|
|
34
|
+
primary_key :id
|
|
35
|
+
column :hash160, String, :null => false, :index => true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
create_table :addr_txout do
|
|
39
|
+
column :addr_id, :int, :null => false, :index => true
|
|
40
|
+
column :txout_id, :int, :null => false, :index => true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
create_table :names do
|
|
44
|
+
column :txout_id, :int, :null => false, :index => true
|
|
45
|
+
column :hash, binary, :index => true
|
|
46
|
+
column :name, binary, :index => true
|
|
47
|
+
column :value, binary
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
|
|
3
|
+
up do
|
|
4
|
+
|
|
5
|
+
$stdout.puts "Running migration #{__FILE__}"
|
|
6
|
+
|
|
7
|
+
next if tables.include?(:tx)
|
|
8
|
+
|
|
9
|
+
create_table :tx do
|
|
10
|
+
primary_key :id
|
|
11
|
+
column :hash, :varchar, :null => false, :unique => true, :index => true
|
|
12
|
+
column :version, :bigint, :null => false
|
|
13
|
+
column :lock_time, :bigint, :null => false
|
|
14
|
+
column :coinbase, :bool, :null => false
|
|
15
|
+
column :tx_size, :int, :null => false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
create_table :blk_tx do
|
|
19
|
+
column :blk_id, :int, :null => false, :index => true
|
|
20
|
+
column :tx_id, :int, :null => false, :index => true
|
|
21
|
+
column :idx, :int, :null => false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
create_table :txin do
|
|
25
|
+
primary_key :id
|
|
26
|
+
column :tx_id, :int, :null => false, :index => true
|
|
27
|
+
column :tx_idx, :int, :null => false
|
|
28
|
+
column :script_sig, :varchar, :null => false
|
|
29
|
+
column :prev_out, :varchar, :null => false, :index => true
|
|
30
|
+
column :prev_out_index, :bigint, :null => false
|
|
31
|
+
column :sequence, :bigint, :null => false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
create_table :txout do
|
|
35
|
+
primary_key :id
|
|
36
|
+
column :tx_id, :int, :null => false, :index => true
|
|
37
|
+
column :tx_idx, :int, :null => false
|
|
38
|
+
column :pk_script, (@db.adapter_scheme == :postgres ? :bytea : :blob), :null => false
|
|
39
|
+
column :value, :bigint
|
|
40
|
+
column :type, :int, :null => false, :index => true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
create_view(:unconfirmed,
|
|
44
|
+
"SELECT * FROM tx WHERE NOT EXISTS " +
|
|
45
|
+
"(SELECT 1 FROM blk_tx WHERE blk_tx.tx_id = tx.id)" +
|
|
46
|
+
"ORDER BY tx.id DESC")
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Sequel.migration do
|
|
2
|
+
|
|
3
|
+
up do
|
|
4
|
+
|
|
5
|
+
$stdout.puts "Running migration #{__FILE__}"
|
|
6
|
+
|
|
7
|
+
if adapter_scheme == :postgres
|
|
8
|
+
add_column :txin, :tmp_script_sig, :bytea
|
|
9
|
+
self[:txin].where.update("tmp_script_sig = script_sig::bytea")
|
|
10
|
+
drop_column :txin, :script_sig
|
|
11
|
+
add_column :txin, :script_sig, :bytea
|
|
12
|
+
self[:txin].where.update("script_sig = tmp_script_sig")
|
|
13
|
+
drop_column :txin, :tmp_script_sig
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
Bitcoin.require_dependency :sequel, message:
|
|
4
|
+
"Note: You will also need an adapter for your database like sqlite3, mysql2, postgresql"
|
|
5
|
+
|
|
6
|
+
module Bitcoin::Storage::Backends
|
|
7
|
+
|
|
8
|
+
# Storage backend using Sequel to connect to arbitrary SQL databases.
|
|
9
|
+
# Inherits from StoreBase and implements its interface.
|
|
10
|
+
class SequelStore < StoreBase
|
|
11
|
+
|
|
12
|
+
# sequel database connection
|
|
13
|
+
attr_accessor :db
|
|
14
|
+
|
|
15
|
+
DEFAULT_CONFIG = { mode: :full, cache_head: false }
|
|
16
|
+
|
|
17
|
+
# create sequel store with given +config+
|
|
18
|
+
def initialize config, *args
|
|
19
|
+
super config, *args
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# connect to database
|
|
23
|
+
def connect
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# reset database; delete all data
|
|
28
|
+
def reset
|
|
29
|
+
[:blk, :blk_tx, :tx, :txin, :txout, :addr, :addr_txout, :names].each {|table| @db[table].delete }
|
|
30
|
+
@head = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# persist given block +blk+ to storage.
|
|
34
|
+
def persist_block blk, chain, depth, prev_work = 0
|
|
35
|
+
@db.transaction do
|
|
36
|
+
attrs = {
|
|
37
|
+
:hash => blk.hash.htb.blob,
|
|
38
|
+
:depth => depth,
|
|
39
|
+
:chain => chain,
|
|
40
|
+
:version => blk.ver,
|
|
41
|
+
:prev_hash => blk.prev_block.reverse.blob,
|
|
42
|
+
:mrkl_root => blk.mrkl_root.reverse.blob,
|
|
43
|
+
:time => blk.time,
|
|
44
|
+
:bits => blk.bits,
|
|
45
|
+
:nonce => blk.nonce,
|
|
46
|
+
:blk_size => blk.to_payload.bytesize,
|
|
47
|
+
:work => (prev_work + blk.block_work).to_s
|
|
48
|
+
}
|
|
49
|
+
attrs[:aux_pow] = blk.aux_pow.to_payload.blob if blk.aux_pow
|
|
50
|
+
existing = @db[:blk].filter(:hash => blk.hash.htb.blob)
|
|
51
|
+
if existing.any?
|
|
52
|
+
existing.update attrs
|
|
53
|
+
block_id = existing.first[:id]
|
|
54
|
+
else
|
|
55
|
+
block_id = @db[:blk].insert(attrs)
|
|
56
|
+
blk_tx, new_tx, addrs, names = [], [], [], []
|
|
57
|
+
|
|
58
|
+
# store tx
|
|
59
|
+
blk.tx.each.with_index do |tx, idx|
|
|
60
|
+
existing = @db[:tx][hash: tx.hash.htb.blob]
|
|
61
|
+
existing ? blk_tx[idx] = existing[:id] : new_tx << [tx, idx]
|
|
62
|
+
end
|
|
63
|
+
new_tx_ids = @db[:tx].insert_multiple(new_tx.map {|tx, _| tx_data(tx) })
|
|
64
|
+
new_tx_ids.each.with_index {|tx_id, idx| blk_tx[new_tx[idx][1]] = tx_id }
|
|
65
|
+
|
|
66
|
+
@db[:blk_tx].insert_multiple(blk_tx.map.with_index {|id, idx|
|
|
67
|
+
{ blk_id: block_id, tx_id: id, idx: idx } })
|
|
68
|
+
|
|
69
|
+
# store txins
|
|
70
|
+
txin_ids = @db[:txin].insert_multiple(new_tx.map.with_index {|tx, tx_idx|
|
|
71
|
+
tx, _ = *tx
|
|
72
|
+
tx.in.map.with_index {|txin, txin_idx|
|
|
73
|
+
txin_data(new_tx_ids[tx_idx], txin, txin_idx) } }.flatten)
|
|
74
|
+
|
|
75
|
+
# store txouts
|
|
76
|
+
txout_i = 0
|
|
77
|
+
txout_ids = @db[:txout].insert_multiple(new_tx.map.with_index {|tx, tx_idx|
|
|
78
|
+
tx, _ = *tx
|
|
79
|
+
tx.out.map.with_index {|txout, txout_idx|
|
|
80
|
+
script_type, a, n = *parse_script(txout, txout_i)
|
|
81
|
+
addrs += a; names += n; txout_i += 1
|
|
82
|
+
txout_data(new_tx_ids[tx_idx], txout, txout_idx, script_type) } }.flatten)
|
|
83
|
+
|
|
84
|
+
# store addrs
|
|
85
|
+
persist_addrs addrs.map {|i, h| [txout_ids[i], h]}
|
|
86
|
+
names.each {|i, script| store_name(script, txout_ids[i]) }
|
|
87
|
+
end
|
|
88
|
+
@head = wrap_block(attrs.merge(id: block_id)) if chain == MAIN
|
|
89
|
+
@db[:blk].where(:prev_hash => blk.hash.htb.blob, :chain => ORPHAN).each do |b|
|
|
90
|
+
log.debug { "connecting orphan #{b[:hash].hth}" }
|
|
91
|
+
begin
|
|
92
|
+
store_block(get_block(b[:hash].hth))
|
|
93
|
+
rescue SystemStackError
|
|
94
|
+
EM.defer { store_block(get_block(b[:hash].hth)) } if EM.reactor_running?
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
return depth, chain
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def reorg new_side, new_main
|
|
102
|
+
@db.transaction do
|
|
103
|
+
@db[:blk].where(hash: new_side.map {|h| h.htb.blob }).update(chain: SIDE)
|
|
104
|
+
new_main.each {|b| get_block(b).validator(self).validate(raise_errors: true) } unless @config[:skip_validation]
|
|
105
|
+
@db[:blk].where(hash: new_main.map {|h| h.htb.blob }).update(chain: MAIN)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# parse script and collect address/txout mappings to index
|
|
110
|
+
def parse_script txout, i
|
|
111
|
+
addrs, names = [], []
|
|
112
|
+
|
|
113
|
+
script = Bitcoin::Script.new(txout.pk_script) rescue nil
|
|
114
|
+
if script
|
|
115
|
+
if script.is_hash160? || script.is_pubkey?
|
|
116
|
+
addrs << [i, script.get_hash160]
|
|
117
|
+
elsif script.is_multisig?
|
|
118
|
+
script.get_multisig_pubkeys.map do |pubkey|
|
|
119
|
+
addrs << [i, Bitcoin.hash160(pubkey.unpack("H*")[0])]
|
|
120
|
+
end
|
|
121
|
+
elsif Bitcoin.namecoin? && script.is_namecoin?
|
|
122
|
+
addrs << [i, script.get_hash160]
|
|
123
|
+
names << [i, script]
|
|
124
|
+
else
|
|
125
|
+
log.warn { "Unknown script type"}# #{tx.hash}:#{txout_idx}" }
|
|
126
|
+
end
|
|
127
|
+
script_type = SCRIPT_TYPES.index(script.type)
|
|
128
|
+
else
|
|
129
|
+
log.error { "Error parsing script"}# #{tx.hash}:#{txout_idx}" }
|
|
130
|
+
script_type = SCRIPT_TYPES.index(:unknown)
|
|
131
|
+
end
|
|
132
|
+
[script_type, addrs, names]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# bulk-store addresses and txout mappings
|
|
136
|
+
def persist_addrs addrs
|
|
137
|
+
addr_txouts, new_addrs = [], []
|
|
138
|
+
addrs.group_by {|_, a| a }.each do |hash160, txouts|
|
|
139
|
+
if existing = @db[:addr][:hash160 => hash160]
|
|
140
|
+
txouts.each {|id, _| addr_txouts << [existing[:id], id] }
|
|
141
|
+
else
|
|
142
|
+
new_addrs << [hash160, txouts.map {|id, _| id }]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
new_addr_ids = @db[:addr].insert_multiple(new_addrs.map {|hash160, txout_id|
|
|
146
|
+
{ hash160: hash160 } })
|
|
147
|
+
new_addr_ids.each.with_index do |addr_id, idx|
|
|
148
|
+
new_addrs[idx][1].each do |txout_id|
|
|
149
|
+
addr_txouts << [addr_id, txout_id]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
@db[:addr_txout].insert_multiple(addr_txouts.map {|addr_id, txout_id|
|
|
153
|
+
{ addr_id: addr_id, txout_id: txout_id }})
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# prepare transaction data for storage
|
|
157
|
+
def tx_data tx
|
|
158
|
+
{ hash: tx.hash.htb.blob,
|
|
159
|
+
version: tx.ver, lock_time: tx.lock_time,
|
|
160
|
+
coinbase: tx.in.size == 1 && tx.in[0].coinbase?,
|
|
161
|
+
tx_size: tx.payload.bytesize }
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# store transaction +tx+
|
|
165
|
+
def store_tx(tx, validate = true)
|
|
166
|
+
@log.debug { "Storing tx #{tx.hash} (#{tx.to_payload.bytesize} bytes)" }
|
|
167
|
+
tx.validator(self).validate(raise_errors: true) if validate
|
|
168
|
+
@db.transaction do
|
|
169
|
+
transaction = @db[:tx][:hash => tx.hash.htb.blob]
|
|
170
|
+
return transaction[:id] if transaction
|
|
171
|
+
tx_id = @db[:tx].insert(tx_data(tx))
|
|
172
|
+
tx.in.each_with_index {|i, idx| store_txin(tx_id, i, idx)}
|
|
173
|
+
tx.out.each_with_index {|o, idx| store_txout(tx_id, o, idx)}
|
|
174
|
+
tx_id
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# prepare txin data for storage
|
|
179
|
+
def txin_data tx_id, txin, idx
|
|
180
|
+
{ tx_id: tx_id, tx_idx: idx,
|
|
181
|
+
script_sig: txin.script_sig.blob,
|
|
182
|
+
prev_out: txin.prev_out.blob,
|
|
183
|
+
prev_out_index: txin.prev_out_index,
|
|
184
|
+
sequence: txin.sequence.unpack("V")[0] }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# store input +txin+
|
|
188
|
+
def store_txin(tx_id, txin, idx)
|
|
189
|
+
@db[:txin].insert(txin_data(tx_id, txin, idx))
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# prepare txout data for storage
|
|
193
|
+
def txout_data tx_id, txout, idx, script_type
|
|
194
|
+
{ tx_id: tx_id, tx_idx: idx,
|
|
195
|
+
pk_script: txout.pk_script.blob,
|
|
196
|
+
value: txout.value, type: script_type }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# store output +txout+
|
|
200
|
+
def store_txout(tx_id, txout, idx)
|
|
201
|
+
script_type, addrs, names = *parse_script(txout, idx)
|
|
202
|
+
txout_id = @db[:txout].insert(txout_data(tx_id, txout, idx, script_type))
|
|
203
|
+
persist_addrs addrs.map {|i, h| [txout_id, h] }
|
|
204
|
+
names.each {|i, script| store_name(script, txout_id) }
|
|
205
|
+
txout_id
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# delete transaction
|
|
209
|
+
# TODO: also delete blk_tx mapping
|
|
210
|
+
def delete_tx(hash)
|
|
211
|
+
log.debug { "Deleting tx #{hash} since all its outputs are spent" }
|
|
212
|
+
@db.transaction do
|
|
213
|
+
tx = get_tx(hash)
|
|
214
|
+
tx.in.each {|i| @db[:txin].where(:id => i.id).delete }
|
|
215
|
+
tx.out.each {|o| @db[:txout].where(:id => o.id).delete }
|
|
216
|
+
@db[:tx].where(:id => tx.id).delete
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# check if block +blk_hash+ exists
|
|
221
|
+
def has_block(blk_hash)
|
|
222
|
+
!!@db[:blk].where(:hash => blk_hash.htb.blob).get(1)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# check if transaction +tx_hash+ exists
|
|
226
|
+
def has_tx(tx_hash)
|
|
227
|
+
!!@db[:tx].where(:hash => tx_hash.htb.blob).get(1)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# get head block (highest block from the MAIN chain)
|
|
231
|
+
def get_head
|
|
232
|
+
(@config[:cache_head] && @head) ? @head :
|
|
233
|
+
@head = wrap_block(@db[:blk].filter(:chain => MAIN).order(:depth).last)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def get_head_hash
|
|
237
|
+
(@config[:cache_head] && @head) ? @head.hash :
|
|
238
|
+
@head = @db[:blk].filter(:chain => MAIN).order(:depth).last[:hash].hth
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# get depth of MAIN chain
|
|
242
|
+
def get_depth
|
|
243
|
+
depth = (@config[:cache_head] && @head) ? @head.depth :
|
|
244
|
+
@depth = @db[:blk].filter(:chain => MAIN).order(:depth).last[:depth] rescue nil
|
|
245
|
+
|
|
246
|
+
return -1 unless depth
|
|
247
|
+
depth
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# get block for given +blk_hash+
|
|
251
|
+
def get_block(blk_hash)
|
|
252
|
+
wrap_block(@db[:blk][:hash => blk_hash.htb.blob])
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# get block by given +depth+
|
|
256
|
+
def get_block_by_depth(depth)
|
|
257
|
+
wrap_block(@db[:blk][:depth => depth, :chain => MAIN])
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# get block by given +prev_hash+
|
|
261
|
+
def get_block_by_prev_hash(prev_hash)
|
|
262
|
+
wrap_block(@db[:blk][:prev_hash => prev_hash.htb.blob, :chain => MAIN])
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# get block by given +tx_hash+
|
|
266
|
+
def get_block_by_tx(tx_hash)
|
|
267
|
+
tx = @db[:tx][:hash => tx_hash.htb.blob]
|
|
268
|
+
return nil unless tx
|
|
269
|
+
parent = @db[:blk_tx][:tx_id => tx[:id]]
|
|
270
|
+
return nil unless parent
|
|
271
|
+
wrap_block(@db[:blk][:id => parent[:blk_id]])
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# get block by given +id+
|
|
275
|
+
def get_block_by_id(block_id)
|
|
276
|
+
wrap_block(@db[:blk][:id => block_id])
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# get transaction for given +tx_hash+
|
|
280
|
+
def get_tx(tx_hash)
|
|
281
|
+
wrap_tx(@db[:tx][:hash => tx_hash.htb.blob])
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# get transaction by given +tx_id+
|
|
285
|
+
def get_tx_by_id(tx_id)
|
|
286
|
+
wrap_tx(@db[:tx][:id => tx_id])
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# get corresponding Models::TxIn for the txout in transaction
|
|
290
|
+
# +tx_hash+ with index +txout_idx+
|
|
291
|
+
def get_txin_for_txout(tx_hash, txout_idx)
|
|
292
|
+
tx_hash = tx_hash.htb_reverse.blob
|
|
293
|
+
wrap_txin(@db[:txin][:prev_out => tx_hash, :prev_out_index => txout_idx])
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def get_txout_by_id(txout_id)
|
|
297
|
+
wrap_txout(@db[:txout][:id => txout_id])
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# get corresponding Models::TxOut for +txin+
|
|
301
|
+
def get_txout_for_txin(txin)
|
|
302
|
+
tx = @db[:tx][:hash => txin.prev_out.reverse.blob]
|
|
303
|
+
return nil unless tx
|
|
304
|
+
wrap_txout(@db[:txout][:tx_idx => txin.prev_out_index, :tx_id => tx[:id]])
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# get all Models::TxOut matching given +script+
|
|
308
|
+
def get_txouts_for_pk_script(script)
|
|
309
|
+
txouts = @db[:txout].filter(:pk_script => script.blob).order(:id)
|
|
310
|
+
txouts.map{|txout| wrap_txout(txout)}
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# get all Models::TxOut matching given +hash160+
|
|
314
|
+
def get_txouts_for_hash160(hash160, unconfirmed = false)
|
|
315
|
+
addr = @db[:addr][:hash160 => hash160]
|
|
316
|
+
return [] unless addr
|
|
317
|
+
txouts = @db[:addr_txout].where(:addr_id => addr[:id])
|
|
318
|
+
.map{|t| @db[:txout][:id => t[:txout_id]] }
|
|
319
|
+
.map{|o| wrap_txout(o) }
|
|
320
|
+
unless unconfirmed
|
|
321
|
+
txouts.select!{|o| @db[:blk][:id => o.get_tx.blk_id][:chain] == MAIN rescue false }
|
|
322
|
+
end
|
|
323
|
+
txouts
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def get_txouts_for_name_hash(hash)
|
|
327
|
+
@db[:names].filter(hash: hash).map {|n| get_txout_by_id(n[:txout_id]) }
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
# get all unconfirmed Models::TxOut
|
|
331
|
+
def get_unconfirmed_tx
|
|
332
|
+
@db[:unconfirmed].map{|t| wrap_tx(t)}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Grab the position of a tx in a given block
|
|
336
|
+
def get_idx_from_tx_hash(tx_hash)
|
|
337
|
+
tx = @db[:tx][:hash => tx_hash.htb.blob]
|
|
338
|
+
return nil unless tx
|
|
339
|
+
parent = @db[:blk_tx][:tx_id => tx[:id]]
|
|
340
|
+
return nil unless parent
|
|
341
|
+
return parent[:idx]
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# wrap given +block+ into Models::Block
|
|
345
|
+
def wrap_block(block)
|
|
346
|
+
return nil unless block
|
|
347
|
+
|
|
348
|
+
data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain], :work => block[:work].to_i, :hash => block[:hash].hth}
|
|
349
|
+
blk = Bitcoin::Storage::Models::Block.new(self, data)
|
|
350
|
+
|
|
351
|
+
blk.ver = block[:version]
|
|
352
|
+
blk.prev_block = block[:prev_hash].reverse
|
|
353
|
+
blk.mrkl_root = block[:mrkl_root].reverse
|
|
354
|
+
blk.time = block[:time].to_i
|
|
355
|
+
blk.bits = block[:bits]
|
|
356
|
+
blk.nonce = block[:nonce]
|
|
357
|
+
|
|
358
|
+
blk.aux_pow = Bitcoin::P::AuxPow.new(block[:aux_pow]) if block[:aux_pow]
|
|
359
|
+
|
|
360
|
+
db[:blk_tx].filter(blk_id: block[:id]).join(:tx, id: :tx_id)
|
|
361
|
+
.order(:idx).each {|tx| blk.tx << wrap_tx(tx, block[:id]) }
|
|
362
|
+
|
|
363
|
+
blk.recalc_block_hash
|
|
364
|
+
blk
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# wrap given +transaction+ into Models::Transaction
|
|
368
|
+
def wrap_tx(transaction, block_id = nil)
|
|
369
|
+
return nil unless transaction
|
|
370
|
+
|
|
371
|
+
block_id ||= @db[:blk_tx].join(:blk, id: :blk_id)
|
|
372
|
+
.where(tx_id: transaction[:id], chain: 0).first[:blk_id] rescue nil
|
|
373
|
+
|
|
374
|
+
data = {id: transaction[:id], blk_id: block_id}
|
|
375
|
+
tx = Bitcoin::Storage::Models::Tx.new(self, data)
|
|
376
|
+
|
|
377
|
+
inputs = db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
|
378
|
+
inputs.each { |i| tx.add_in(wrap_txin(i)) }
|
|
379
|
+
|
|
380
|
+
outputs = db[:txout].filter(:tx_id => transaction[:id]).order(:tx_idx)
|
|
381
|
+
outputs.each { |o| tx.add_out(wrap_txout(o)) }
|
|
382
|
+
tx.ver = transaction[:version]
|
|
383
|
+
tx.lock_time = transaction[:lock_time]
|
|
384
|
+
tx.hash = tx.hash_from_payload(tx.to_payload)
|
|
385
|
+
tx
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
# wrap given +input+ into Models::TxIn
|
|
389
|
+
def wrap_txin(input)
|
|
390
|
+
return nil unless input
|
|
391
|
+
data = {:id => input[:id], :tx_id => input[:tx_id], :tx_idx => input[:tx_idx]}
|
|
392
|
+
txin = Bitcoin::Storage::Models::TxIn.new(self, data)
|
|
393
|
+
txin.prev_out = input[:prev_out]
|
|
394
|
+
txin.prev_out_index = input[:prev_out_index]
|
|
395
|
+
txin.script_sig_length = input[:script_sig].bytesize
|
|
396
|
+
txin.script_sig = input[:script_sig]
|
|
397
|
+
txin.sequence = [input[:sequence]].pack("V")
|
|
398
|
+
txin
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# wrap given +output+ into Models::TxOut
|
|
402
|
+
def wrap_txout(output)
|
|
403
|
+
return nil unless output
|
|
404
|
+
data = {:id => output[:id], :tx_id => output[:tx_id], :tx_idx => output[:tx_idx],
|
|
405
|
+
:hash160 => output[:hash160], :type => SCRIPT_TYPES[output[:type]]}
|
|
406
|
+
txout = Bitcoin::Storage::Models::TxOut.new(self, data)
|
|
407
|
+
txout.value = output[:value]
|
|
408
|
+
txout.pk_script = output[:pk_script]
|
|
409
|
+
txout
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
# check data consistency of the top +count+ blocks. validates that
|
|
413
|
+
# - the block hash computed from the stored data is the same
|
|
414
|
+
# - the prev_hash is the same as the previous blocks' hash
|
|
415
|
+
# - the merkle root computed from all transactions is correct
|
|
416
|
+
def check_consistency count = 1000
|
|
417
|
+
return if get_depth < 1 || count <= 0
|
|
418
|
+
depth = get_depth
|
|
419
|
+
count = depth - 1 if count == -1
|
|
420
|
+
count = depth - 1 if count >= depth
|
|
421
|
+
log.info { "Checking consistency of last #{count} blocks..." }
|
|
422
|
+
prev_blk = get_block_by_depth(depth - count - 1)
|
|
423
|
+
(depth - count).upto(depth).each do |depth|
|
|
424
|
+
blk = get_block_by_depth(depth)
|
|
425
|
+
raise "Block hash #{blk.depth} invalid!" unless blk.hash == blk.recalc_block_hash
|
|
426
|
+
raise "Prev hash #{blk.depth} invalid!" unless blk.prev_block.reverse.hth == prev_blk.hash
|
|
427
|
+
raise "Merkle root #{blk.depth} invalid!" unless blk.verify_mrkl_root
|
|
428
|
+
print "#{blk.hash} #{blk.depth} OK\r"
|
|
429
|
+
prev_blk = blk
|
|
430
|
+
end
|
|
431
|
+
log.info { "Last #{count} blocks are consistent." }
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
end
|