bitcoin-ruby 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. 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 store_block(blk)
16
- return false unless blk
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
- get_depth
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(hth(@prev_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.reverse.unpack("H*")[0])
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
- Bitcoin::Script.new(@pk_script).get_hash160
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
- Bitcoin::Script.new(@pk_script).get_address
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
- Bitcoin::Script.new(@pk_script).get_addresses
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
- Bitcoin::Script.new(@pk_script).type
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, :bytea, :null => false, :unique => true, :index => true
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, :bytea, :null => false, :index => true
11
- column :mrkl_root, :bytea, :null => false
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, :bytea, :null => false, :unique => true, :index => true
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, :bytea, :null => false
45
- column :prev_out, :bytea, :null => false, :index => true
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, :bytea, :null => false, :index => true
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