bitcoin-ruby 0.0.5 → 0.0.6

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 (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +2 -2
  4. data/COPYING +1 -1
  5. data/Gemfile +5 -11
  6. data/README.rdoc +11 -5
  7. data/Rakefile +5 -0
  8. data/bin/bitcoin_node +11 -29
  9. data/bin/bitcoin_node_cli +81 -0
  10. data/bin/bitcoin_wallet +9 -6
  11. data/doc/NODE.rdoc +79 -26
  12. data/examples/bbe_verify_tx.rb +1 -1
  13. data/examples/index_nhash.rb +24 -0
  14. data/examples/reindex_p2sh_addrs.rb +44 -0
  15. data/lib/bitcoin.rb +135 -20
  16. data/lib/bitcoin/builder.rb +233 -63
  17. data/lib/bitcoin/key.rb +89 -16
  18. data/lib/bitcoin/litecoin.rb +13 -11
  19. data/lib/bitcoin/namecoin.rb +5 -4
  20. data/lib/bitcoin/network/command_client.rb +23 -13
  21. data/lib/bitcoin/network/command_handler.rb +336 -131
  22. data/lib/bitcoin/network/connection_handler.rb +14 -13
  23. data/lib/bitcoin/network/node.rb +61 -20
  24. data/lib/bitcoin/protocol.rb +5 -1
  25. data/lib/bitcoin/protocol/block.rb +15 -3
  26. data/lib/bitcoin/protocol/parser.rb +3 -3
  27. data/lib/bitcoin/protocol/tx.rb +82 -20
  28. data/lib/bitcoin/protocol/txin.rb +7 -0
  29. data/lib/bitcoin/protocol/txout.rb +12 -9
  30. data/lib/bitcoin/script.rb +329 -75
  31. data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
  32. data/lib/bitcoin/storage/models.rb +6 -11
  33. data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
  34. data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
  35. data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
  36. data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
  37. data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
  38. data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
  39. data/lib/bitcoin/storage/storage.rb +161 -97
  40. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
  41. data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
  42. data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
  43. data/lib/bitcoin/validation.rb +87 -56
  44. data/lib/bitcoin/version.rb +1 -1
  45. data/spec/bitcoin/bitcoin_spec.rb +38 -0
  46. data/spec/bitcoin/builder_spec.rb +177 -0
  47. data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
  48. data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
  49. data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
  50. data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
  51. data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
  52. data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
  53. data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
  54. data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
  55. data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
  56. data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
  57. data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
  58. data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
  59. data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
  60. data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
  61. data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
  62. data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
  63. data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
  64. data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
  65. data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
  66. data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
  67. data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
  68. data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
  69. data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
  70. data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
  71. data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
  72. data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
  73. data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
  74. data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
  75. data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
  76. data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
  77. data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
  78. data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
  79. data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
  80. data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
  81. data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
  82. data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
  83. data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
  84. data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
  85. data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
  86. data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
  87. data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
  88. data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
  89. data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
  90. data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
  91. data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
  92. data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
  93. data/spec/bitcoin/key_spec.rb +79 -8
  94. data/spec/bitcoin/namecoin_spec.rb +1 -1
  95. data/spec/bitcoin/node/command_api_spec.rb +373 -86
  96. data/spec/bitcoin/performance/storage_spec.rb +41 -0
  97. data/spec/bitcoin/protocol/addr_spec.rb +7 -5
  98. data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
  99. data/spec/bitcoin/protocol/block_spec.rb +6 -0
  100. data/spec/bitcoin/protocol/tx_spec.rb +184 -1
  101. data/spec/bitcoin/protocol/txin_spec.rb +27 -0
  102. data/spec/bitcoin/protocol/txout_spec.rb +27 -0
  103. data/spec/bitcoin/script/opcodes_spec.rb +74 -3
  104. data/spec/bitcoin/script/script_spec.rb +271 -0
  105. data/spec/bitcoin/spec_helper.rb +34 -6
  106. data/spec/bitcoin/storage/models_spec.rb +104 -0
  107. data/spec/bitcoin/storage/reorg_spec.rb +42 -11
  108. data/spec/bitcoin/storage/storage_spec.rb +58 -15
  109. data/spec/bitcoin/storage/validation_spec.rb +44 -14
  110. data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
  111. data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
  112. data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
  113. metadata +117 -11
@@ -41,7 +41,7 @@ module Bitcoin::Network
41
41
  @latency_ms = nil
42
42
  @lock = Monitor.new
43
43
  @last_getblocks = [] # the last few getblocks messages received
44
- rescue Exception
44
+ rescue
45
45
  log.fatal { "Error in #initialize" }
46
46
  p $!; puts $@; exit
47
47
  end
@@ -51,15 +51,16 @@ module Bitcoin::Network
51
51
  if incoming?
52
52
  begin_handshake
53
53
  end
54
- rescue Exception
54
+ rescue
55
55
  log.fatal { "Error in #post_init" }
56
56
  p $!; puts *$@
57
57
  end
58
58
 
59
59
  # only called for outgoing connection
60
60
  def connection_completed
61
+ @connection_completed = true
61
62
  begin_handshake
62
- rescue Exception
63
+ rescue
63
64
  log.fatal { "Error in #connection_completed" }
64
65
  p $!; puts *$@
65
66
  end
@@ -75,8 +76,8 @@ module Bitcoin::Network
75
76
 
76
77
  # connection closed; notify listeners and cleanup connection from node
77
78
  def unbind
78
- log.info { "Disconnected" }
79
- @node.push_notification(:connection, [:disconnected, [@host, @port]])
79
+ log.info { (outgoing? && !@connection_completed) ? "Connection failed" : "Disconnected" }
80
+ @node.push_notification(:connection, {type: :disconnected, host: @host, port: @port})
80
81
  @state = :disconnected
81
82
  @node.connections.delete(self)
82
83
  end
@@ -90,9 +91,8 @@ module Bitcoin::Network
90
91
  log.info { "Established #{@direction} connection" }
91
92
  @node.connections << self
92
93
  @state = :handshake
93
- # incoming connections wait to receive a version
94
- send_version if outgoing?
95
- rescue Exception
94
+ send_version
95
+ rescue
96
96
  log.fatal { "Error in #begin_handshake" }
97
97
  p $!; puts *$@
98
98
  end
@@ -103,9 +103,10 @@ module Bitcoin::Network
103
103
  log.debug { 'Handshake completed' }
104
104
  @state = :connected
105
105
  @started = Time.now
106
- @node.push_notification(:connection, [:connected, info])
106
+ @node.push_notification(:connection, info.merge(type: :connected))
107
107
  @node.addrs << addr
108
108
  end
109
+ send_data P::Addr.pkt(@node.addr) if @node.config[:announce]
109
110
  end
110
111
 
111
112
  # received +inv_tx+ message for given +hash+.
@@ -223,15 +224,15 @@ module Bitcoin::Network
223
224
 
224
225
  return unless depth && depth <= @node.store.get_depth
225
226
  range = (depth+1..depth+500)
226
- blocks = @node.store.db[:blk].where(chain: 0, depth: range).select(:hash).all +
227
- [@node.store.db[:blk].select(:hash)[chain: 0, depth: depth+502]]
227
+ blocks = @node.store.db[:blk].where(chain: 0, depth: range).order(:depth).select(:hash).all
228
228
  send_inv(:block, *blocks.map {|b| b[:hash].hth })
229
229
  end
230
230
 
231
231
  # received +getaddr+ message.
232
232
  # send +addr+ message with peer addresses back.
233
233
  def on_getaddr
234
- addrs = @node.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250]
234
+ addrs = @node.config[:announce] ? [@node.addr] : []
235
+ addrs += @node.addrs.select{|a| a.time > Time.now.to_i - 10800 }.shuffle[0..250]
235
236
  log.debug { "<< addr (#{addrs.size})" }
236
237
  send_data P::Addr.pkt(*addrs)
237
238
  end
@@ -311,7 +312,7 @@ module Bitcoin::Network
311
312
  send_data(Protocol.ping_pkt(@ping_nonce))
312
313
  else
313
314
  # set latency to 5 seconds, terrible but this version should be obsolete now
314
- @latency_ms = (5*1000)
315
+ @latency_ms = (5*1000)
315
316
  log.debug { "<< ping" }
316
317
  send_data(Protocol.ping_pkt)
317
318
  end
@@ -50,11 +50,16 @@ module Bitcoin::Network
50
50
 
51
51
  DEFAULT_CONFIG = {
52
52
  :network => :bitcoin,
53
- :listen => ["0.0.0.0", Bitcoin.network[:default_port]],
53
+ :listen => ["0.0.0.0", nil],
54
54
  :connect => [],
55
55
  :command => ["127.0.0.1", 9999],
56
56
  :storage => "utxo::sqlite://~/.bitcoin-ruby/<network>/blocks.db",
57
+ :announce => false,
58
+ :external_port => nil,
57
59
  :mode => :full,
60
+ :cache_head => true,
61
+ :index_nhash => false,
62
+ :index_p2sh_type => false,
58
63
  :dns => true,
59
64
  :epoll_limit => 10000,
60
65
  :epoll_user => nil,
@@ -102,8 +107,9 @@ module Bitcoin::Network
102
107
  def set_store
103
108
  backend, config = @config[:storage].split('::')
104
109
  @store = Bitcoin::Storage.send(backend, {
105
- db: config, mode: @config[:mode], cache_head: true,
106
- skip_validation: @config[:skip_validation],
110
+ db: config, mode: @config[:mode], cache_head: @config[:cache_head],
111
+ skip_validation: @config[:skip_validation], index_nhash: @config[:index_nhash],
112
+ index_p2sh_type: @config[:index_p2sh_type],
107
113
  log_level: @config[:log][:storage]}, ->(locator) {
108
114
  peer = @connections.select(&:connected?).sample
109
115
  peer.send_getblocks(locator)
@@ -251,6 +257,31 @@ module Bitcoin::Network
251
257
  self.stop
252
258
  end
253
259
 
260
+ subscribe(:block) do |blk, depth|
261
+ next unless @store.in_sync?
262
+ @log.debug { "Relaying block #{blk.hash}" }
263
+ @connections.each do |conn|
264
+ next unless conn.connected?
265
+ conn.send_inv(:block, blk.hash)
266
+ end
267
+ end
268
+
269
+ @store.subscribe(:block) do |blk, depth, chain|
270
+ if chain == 0 && blk.hash == @store.get_head.hash
271
+ @last_block_time = Time.now
272
+ push_notification(:block, [blk, depth])
273
+ blk.tx.each {|tx| @unconfirmed.delete(tx.hash) }
274
+ end
275
+ getblocks if chain == 2 && @store.in_sync?
276
+ end
277
+
278
+ @store.subscribe(:reorg) do |new_main, new_side|
279
+ @log.warn { "Reorg of #{new_side.size} blocks." }
280
+ new_main.each {|b| @log.debug { "new main: #{b}" } }
281
+ new_side.each {|b| @log.debug { "new side: #{b}" } }
282
+ push_notification(:reorg, [new_main, new_side])
283
+ end
284
+
254
285
  end
255
286
  end
256
287
 
@@ -378,27 +409,13 @@ module Bitcoin::Network
378
409
  while obj = @queue.shift
379
410
  begin
380
411
  if obj[0].to_sym == :block
381
- if res = @store.send("new_#{obj[0]}", obj[1])
382
- if res[1] == 0 && obj[1].hash == @store.get_head.hash
383
- @last_block_time = Time.now
384
- push_notification(:block, [obj[1], res[0]])
385
- obj[1].tx.each {|tx| @unconfirmed.delete(tx.hash) }
386
- end
387
- getblocks if res[1] == 2 && @store.in_sync?
388
- end
412
+ @store.new_block(obj[1])
389
413
  else
390
414
  drop = @unconfirmed.size - @config[:max][:unconfirmed] + 1
391
415
  drop.times { @unconfirmed.shift } if drop > 0
392
416
  unless @unconfirmed[obj[1].hash]
393
417
  @unconfirmed[obj[1].hash] = obj[1]
394
418
  push_notification(:tx, [obj[1], 0])
395
-
396
- if @notifiers[:output]
397
- obj[1].out.each do |out|
398
- address = Bitcoin::Script.new(out.pk_script).get_address
399
- push_notification(:output, [obj[1].hash, address, out.value, 0])
400
- end
401
- end
402
419
  end
403
420
  end
404
421
  rescue Bitcoin::Validation::ValidationError
@@ -456,6 +473,12 @@ module Bitcoin::Network
456
473
  @config[:listen][0]
457
474
  end
458
475
 
476
+ # get the external port
477
+ # assume the same as local port if config option :external_port isn't given explicitly
478
+ def external_port
479
+ @config[:external_port] || @config[:listen][1] || Bitcoin.network[:default_port]
480
+ end
481
+
459
482
  # push notification +message+ to +channel+
460
483
  def push_notification channel, message
461
484
  @notifiers[channel.to_sym].push(message) if @notifiers[channel.to_sym]
@@ -466,12 +489,30 @@ module Bitcoin::Network
466
489
  # see CommandHandler for details.
467
490
  def subscribe channel
468
491
  @notifiers[channel.to_sym] ||= EM::Channel.new
469
- @notifiers[channel.to_sym].subscribe {|*data| yield(*data) }
492
+ @notifiers[channel.to_sym].subscribe do |*data|
493
+ begin
494
+ yield(*data)
495
+ rescue
496
+ p $!; puts *$@
497
+ end
498
+ end
499
+ end
500
+
501
+ def unsubscribe channel, id
502
+ @notifiers[channel.to_sym].unsubscribe(id)
470
503
  end
471
504
 
472
505
  # should the node accept new incoming connections?
473
506
  def accept_connections?
474
- connections.select(&:incoming?).size >= config[:max][:connections_in]
507
+ connections.select(&:incoming?).size < config[:max][:connections_in]
508
+ end
509
+
510
+ # get Addr object for our own server
511
+ def addr
512
+ @addr = Bitcoin::P::Addr.new
513
+ @addr.time, @addr.service, @addr.ip, @addr.port =
514
+ Time.now.tv_sec, (1 << 0), external_ip, external_port
515
+ @addr
475
516
  end
476
517
 
477
518
  end
@@ -12,7 +12,7 @@ module Bitcoin
12
12
 
13
13
  # BIP 0031, pong message, is enabled for all versions AFTER this one
14
14
  BIP0031_VERSION = 60000
15
-
15
+
16
16
  autoload :TxIn, 'bitcoin/protocol/txin'
17
17
  autoload :TxOut, 'bitcoin/protocol/txout'
18
18
  autoload :Tx, 'bitcoin/protocol/tx'
@@ -161,6 +161,10 @@ module Bitcoin
161
161
  pkt "getheaders", locator_payload(version, locator_hashes, stop_hash)
162
162
  end
163
163
 
164
+ def self.headers_pkt(version, blocks)
165
+ pkt "headers", [pack_var_int(blocks.size), blocks.map{|block| block.block_header}.join].join
166
+ end
167
+
164
168
  def self.read_binary_file(path)
165
169
  File.open(path, 'rb'){|f| f.read }
166
170
  end
@@ -73,7 +73,7 @@ module Bitcoin
73
73
  @ver, @prev_block, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack("Va32a32VVV")
74
74
  recalc_block_hash
75
75
 
76
- if (@ver & BLOCK_VERSION_AUXPOW) > 0
76
+ if Bitcoin.network[:project] == :namecoin && (@ver & BLOCK_VERSION_AUXPOW) > 0
77
77
  @aux_pow = AuxPow.new(nil)
78
78
  @aux_pow.parse_data_from_io(buf)
79
79
  end
@@ -99,6 +99,10 @@ module Bitcoin
99
99
  @hash = Bitcoin.block_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
100
100
  end
101
101
 
102
+ def recalc_block_scrypt_hash
103
+ @scrypt_hash = Bitcoin.block_scrypt_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
104
+ end
105
+
102
106
  def recalc_mrkl_root
103
107
  @mrkl_root = Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last.htb_reverse
104
108
  end
@@ -216,20 +220,28 @@ module Bitcoin
216
220
  JSON.pretty_generate( h, options )
217
221
  end
218
222
 
223
+ # block header binary output
224
+ def block_header
225
+ [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce, Protocol.pack_var_int(0)].pack("Va32a32VVVa*")
226
+ end
227
+
219
228
  # read binary block from a file
220
229
  def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
221
230
 
222
231
  # read json block from a file
223
232
  def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
224
233
 
234
+ # Get a Bitcoin::Validation object to validate this block. It needs a +store+
235
+ # to validate against, and optionally takes the +prev_block+ for optimization.
225
236
  def validator(store, prev_block = nil)
226
237
  @validator ||= Bitcoin::Validation::Block.new(self, store, prev_block)
227
238
  end
228
239
 
229
240
  # get the (statistical) amount of work that was needed to generate this block.
230
241
  def block_work
231
- target = Bitcoin.decode_compact_bits(@bits)
232
- (2**256) / (target.to_i(16) + 1)
242
+ target = Bitcoin.decode_compact_bits(@bits).to_i(16)
243
+ return 0 if target <= 0
244
+ (2**256) / (target + 1)
233
245
  end
234
246
 
235
247
  end
@@ -111,8 +111,8 @@ module Bitcoin
111
111
 
112
112
  # https://en.bitcoin.it/wiki/BIP_0035
113
113
  def handle_mempool_request(payload)
114
- return unless @version[:version] >= 60002 # Protocol version >= 60002
115
- return unless (@version[:services] & Bitcoin::Protocol::Version::NODE_NETWORK) == 1 # NODE_NETWORK bit set in Services
114
+ return unless @version.fields[:version] >= 60002 # Protocol version >= 60002
115
+ return unless (@version.fields[:services] & Bitcoin::Protocol::Version::NODE_NETWORK) == 1 # NODE_NETWORK bit set in Services
116
116
  @h.on_mempool if @h.respond_to?(:on_mempool)
117
117
  end
118
118
 
@@ -139,7 +139,7 @@ module Bitcoin
139
139
  def parse_buffer
140
140
  head_magic = Bitcoin::network[:magic_head]
141
141
  head_size = 24
142
- return false if @buf.size <= head_size
142
+ return false if @buf.size < head_size
143
143
 
144
144
  magic, cmd, length, checksum = @buf.unpack("a4A12Va4")
145
145
  payload = @buf[head_size...head_size+length]
@@ -25,6 +25,9 @@ module Bitcoin
25
25
  # lock time
26
26
  attr_accessor :lock_time
27
27
 
28
+ # parsed / evaluated input scripts cached for later use
29
+ attr_reader :scripts
30
+
28
31
  alias :inputs :in
29
32
  alias :outputs :out
30
33
 
@@ -35,12 +38,12 @@ module Bitcoin
35
38
 
36
39
  # return the tx hash in binary format
37
40
  def binary_hash
38
- [@hash].pack("H*").reverse
41
+ @binary_hash ||= [@hash].pack("H*").reverse
39
42
  end
40
43
 
41
44
  # create tx from raw binary +data+
42
45
  def initialize(data=nil)
43
- @ver, @lock_time, @in, @out = 1, 0, [], []
46
+ @ver, @lock_time, @in, @out, @scripts = 1, 0, [], [], []
44
47
  parse_data_from_io(data) if data
45
48
  end
46
49
 
@@ -102,24 +105,28 @@ module Bitcoin
102
105
 
103
106
  # generate a signature hash for input +input_idx+.
104
107
  # either pass the +outpoint_tx+ or the +script_pubkey+ directly.
105
- def signature_hash_for_input(input_idx, outpoint_tx, script_pubkey=nil, hash_type=nil, drop_sigs=nil, script=nil)
108
+ def signature_hash_for_input(input_idx, subscript, hash_type=nil)
106
109
  # https://github.com/bitcoin/bitcoin/blob/e071a3f6c06f41068ad17134189a4ac3073ef76b/script.cpp#L834
107
110
  # http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
108
111
  # https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
109
112
  # https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
110
113
  # https://en.bitcoin.it/wiki/OP_CHECKSIG
111
114
 
115
+ # Note: BitcoinQT checks if input_idx >= @in.size and returns 1 with an error message.
116
+ # But this check is never actually useful because BitcoinQT would crash
117
+ # right before VerifyScript if input index is out of bounds (inside CScriptCheck::operator()()).
118
+ # That's why we don't need to do such a check here.
119
+ #
120
+ # However, if you look at the case SIGHASH_TYPE[:single] below, we must
121
+ # return 1 because it's possible to have more inputs than outputs and BitcoinQT returns 1 as well.
112
122
  return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
113
123
 
114
124
  hash_type ||= SIGHASH_TYPE[:all]
115
125
 
116
126
  pin = @in.map.with_index{|input,idx|
117
127
  if idx == input_idx
118
- script_pubkey ||= outpoint_tx.out[ input.prev_out_index ].pk_script
119
- script_pubkey = script if script # force binary aa script
120
- script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop (slow)
121
- #p Bitcoin::Script.new(script_pubkey).to_string
122
- input.to_payload(script_pubkey)
128
+ subscript = subscript.out[ input.prev_out_index ].script if subscript.respond_to?(:out) # legacy api (outpoint_tx)
129
+ input.to_payload(subscript)
123
130
  else
124
131
  case (hash_type & 0x1f)
125
132
  when SIGHASH_TYPE[:none]; input.to_payload("", "\x00\x00\x00\x00")
@@ -152,16 +159,22 @@ module Bitcoin
152
159
 
153
160
  # verify input signature +in_idx+ against the corresponding
154
161
  # output in +outpoint_tx+
155
- def verify_input_signature(in_idx, outpoint_tx, block_timestamp=Time.now.to_i)
162
+ # outpoint
163
+ def verify_input_signature(in_idx, outpoint_tx_or_script, block_timestamp=Time.now.to_i)
156
164
  outpoint_idx = @in[in_idx].prev_out_index
157
165
  script_sig = @in[in_idx].script_sig
158
- script_pubkey = outpoint_tx.out[outpoint_idx].pk_script
159
- script = script_sig + script_pubkey
166
+
167
+ # If given an entire previous transaction, take the script from it
168
+ script_pubkey = if outpoint_tx_or_script.respond_to?(:out)
169
+ outpoint_tx_or_script.out[outpoint_idx].pk_script
170
+ else
171
+ # Otherwise, it's already a script.
172
+ outpoint_tx_or_script
173
+ end
160
174
 
161
- Bitcoin::Script.new(script).run(block_timestamp) do |pubkey,sig,hash_type,drop_sigs,script|
162
- # this IS the checksig callback, must return true/false
163
- hash = signature_hash_for_input(in_idx, outpoint_tx, nil, hash_type, drop_sigs, script)
164
- #hash = signature_hash_for_input(in_idx, nil, script_pubkey, hash_type, drop_sigs, script)
175
+ @scripts[in_idx] = Bitcoin::Script.new(script_sig, script_pubkey)
176
+ @scripts[in_idx].run(block_timestamp) do |pubkey,sig,hash_type,subscript|
177
+ hash = signature_hash_for_input(in_idx, subscript, hash_type)
165
178
  Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
166
179
  end
167
180
  end
@@ -170,7 +183,7 @@ module Bitcoin
170
183
  def to_hash(options = {})
171
184
  @hash ||= hash_from_payload(to_payload)
172
185
  h = {
173
- 'hash' => @hash, 'ver' => @ver,
186
+ 'hash' => @hash, 'ver' => @ver, # 'nid' => normalized_hash,
174
187
  'vin_sz' => @in.size, 'vout_sz' => @out.size,
175
188
  'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
176
189
  'in' => @in.map{|i| i.to_hash(options) },
@@ -215,13 +228,46 @@ module Bitcoin
215
228
  # read json block from a file
216
229
  def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
217
230
 
218
- def validator(store, block = nil)
219
- @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
231
+ # Get a Bitcoin::Validation object to validate this block. It needs a +store+
232
+ # to validate against, a block to validate tx chains inside one block, and
233
+ # optionally takes the +block_validator+ as an optimization.
234
+ def validator(store, block = nil, block_validator = nil)
235
+ @validator ||= Bitcoin::Validation::Tx.new(self, store, block, block_validator)
220
236
  end
221
237
 
222
238
  def size
223
239
  payload.bytesize
224
240
  end
241
+
242
+ # Checks if transaction is final taking into account height and time
243
+ # of a block in which it is located (or about to be included if it's unconfirmed tx).
244
+ def is_final?(block_height, block_time)
245
+ # No time lock - tx is final.
246
+ return true if lock_time == 0
247
+
248
+ # Time based nLockTime implemented in 0.1.6
249
+ # If lock_time is below the magic threshold treat it as a block height.
250
+ # If lock_time is above the threshold, it's a unix timestamp.
251
+ return true if lock_time < (lock_time < Bitcoin::LOCKTIME_THRESHOLD ? block_height : block_time)
252
+
253
+ inputs.each{|input| return false if !input.is_final? }
254
+
255
+ return true
256
+ end
257
+
258
+ def legacy_sigops_count
259
+ # Note: input scripts normally never have any opcodes since every input script
260
+ # can be statically reduced to a pushdata-only script.
261
+ # However, anyone is allowed to create a non-standard transaction with any opcodes in the inputs.
262
+ count = 0
263
+ self.in.each do |txin|
264
+ count += Bitcoin::Script.new(txin.script_sig).sigops_count_accurate(false)
265
+ end
266
+ self.out.each do |txout|
267
+ count += Bitcoin::Script.new(txout.pk_script).sigops_count_accurate(false)
268
+ end
269
+ count
270
+ end
225
271
 
226
272
  DEFAULT_BLOCK_PRIORITY_SIZE = 27000
227
273
 
@@ -241,13 +287,24 @@ module Bitcoin
241
287
  # multiple transactions instead of one big transaction to avoid fees.
242
288
  # * If we are creating a transaction we allow transactions up to 1,000 bytes
243
289
  # to be considered safe and assume they can likely make it into this section.
244
- min_fee = 0 if tx_size < (mode == :block ? 1_000 : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
290
+ min_fee = 0 if tx_size < (mode == :block ? Bitcoin.network[:free_tx_bytes] : DEFAULT_BLOCK_PRIORITY_SIZE - 1_000)
245
291
  end
246
292
 
247
293
  # This code can be removed after enough miners have upgraded to version 0.9.
248
294
  # Until then, be safe when sending and require a fee if any output is less than CENT
249
295
  if min_fee < base_fee && mode == :block
250
- outputs.each{|output| (min_fee = base_fee; break) if output.value < Bitcoin::CENT }
296
+ outputs.each do |output|
297
+ if output.value < Bitcoin.network[:dust]
298
+ # If per dust fee, then we add min fee for each output less than dust.
299
+ # Otherwise, we set to min fee if there is any output less than dust.
300
+ if Bitcoin.network[:per_dust_fee]
301
+ min_fee += base_fee
302
+ else
303
+ min_fee = base_fee
304
+ break
305
+ end
306
+ end
307
+ end
251
308
  end
252
309
 
253
310
  min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
@@ -258,6 +315,11 @@ module Bitcoin
258
315
  inputs.size == 1 and inputs.first.coinbase?
259
316
  end
260
317
 
318
+ def normalized_hash
319
+ signature_hash_for_input(-1, nil, SIGHASH_TYPE[:all]).unpack("H*")[0]
320
+ end
321
+ alias :nhash :normalized_hash
322
+
261
323
  end
262
324
  end
263
325
  end