bitcoin-ruby 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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