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