bitcoin-ruby 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -12,3 +12,4 @@ coverage/
12
12
  *.conf
13
13
  *.db
14
14
  .rbx/
15
+ /vendor
data/README.rdoc CHANGED
@@ -206,9 +206,9 @@ lib/bitcoin/network/node.rb for examples.
206
206
 
207
207
  === Storage / Database backends
208
208
 
209
- There is support for multiple database backends, but currently the only stable one is
210
- the Bitcoin::Storage::Backends::SequelStore backend. All backends implement the interface
211
- defined in Bitcoin::Storage::Backends::StoreBase and return Bitcoin::Storage::Models.
209
+ There is support for different storage backends. Each backend can be used with
210
+ sqlite, postgres or mysql. All backends implement the interface defined in
211
+ Bitcoin::Storage::Backends::StoreBase and return Bitcoin::Storage::Models.
212
212
 
213
213
  store = Bitcoin::Storage.sequel(:db => "sqlite://bitcoin.db") # in-memory db
214
214
  store.get_head #=> block
@@ -217,8 +217,7 @@ defined in Bitcoin::Storage::Backends::StoreBase and return Bitcoin::Storage::Mo
217
217
  txouts.first.type #=> :pubkey
218
218
  txouts.first.get_address #=> "15yN7NPEpu82sHhB6TzCW5z5aXoamiKeGy"
219
219
 
220
- See Bitcoin::Storage::Backends::StoreBase, Bitcoin::Storage::Backends::SequelStore
221
- and Bitcoin::Storage::Models for details.
220
+ See also STORAGE, Bitcoin::Storage::Backends::UtxoStore, Bitcoin::Storage::Backends::SequelStore and Bitcoin::Storage::Backends::DummyStore for details.
222
221
 
223
222
  == Documentation
224
223
 
data/doc/CONFIG.rdoc CHANGED
@@ -38,7 +38,7 @@ from the +all+ category (ie. +bitcoin_wallet+ loads +all+ and +wallet+).
38
38
  == Default Values
39
39
 
40
40
  all:
41
- network: bitcoin # network identifier ("bitcoin" or "testnet")
41
+ network: bitcoin # network identifier ("bitcoin", "testnet3", "namecoin")
42
42
  command: "127.0.0.1:9999" # IP:Port to listen for incomming command connections
43
43
  listen: "0.0.0.0:8332" # IP:Port to listen for incoming peer connections
44
44
  connect: "" # List of IP:Port,IP:Port of nodes to connect to
data/doc/STORAGE.rdoc CHANGED
@@ -1,13 +1,25 @@
1
1
  = Storage
2
2
 
3
- There is support for different storage backends, currently the `sequel` backend is the most stable and should work with `sqlite` and `postgres` databases. see Bitcoin::Storage
3
+ There is support for different storage backends, currently there are two. Each backend can be used with sqlite, postgres or mysql.
4
4
 
5
5
  == Backends
6
6
 
7
+ === UTXO
8
+
9
+ The `utxo` backend stores only the minimum amount of information needed to validate new blocks, namely the `Unspent TX Outputs`. So it can be used to independently validate the whole blockchain as it goes, but it cannot query old data or serve blocks to peers. There is however a way to tell it specific addresses for which it will keep the history (the ones in your wallet).
10
+
11
+ === Sequel
12
+
13
+ The `sequel` backend stores the whole blockchain into a fully normalized SQL DB. It can be used to run arbitrary queries on the blockchain data, but the database gets huge and it takes a very long time to do an initial sync.
14
+
15
+ It is used to run http://webbtc.com and there are also postgres dumps to get you started faster on http://dumps.webbtc.com.
16
+
17
+
18
+ == Config
19
+
7
20
  For examples that require storage backends, you can specify them using
8
21
  a string containing the backend name and a configuration string.
9
- The default backend is +sequel::sqlite://bitcoin.db+ which is a sqlite database
10
- called +bitcoin.db+ in the current directory
22
+ The default backend is +utxo::sqlite:~/.bitcoin-ruby/<network>blocks.db+, which is a sqlite database called +blocks.db+ in a directory named after the network you're using, inside your homedir.
11
23
 
12
24
  sequel::sqlite:/ # in-memory
13
25
  sequel::sqlite://bitcoin.db
@@ -18,4 +30,4 @@ called +bitcoin.db+ in the current directory
18
30
  == Custom Backends
19
31
 
20
32
  To implement a custom backend you need to inherit from Bitcoin::Storage::Backends::StoreBase
21
- and implement the methods defined there.
33
+ and implement the methods defined there. When returning blocks/tx, you should wrap them in Bitcoin::Storage::Models objects. See the `dummy` backend for a simple example.
data/lib/bitcoin.rb CHANGED
@@ -495,7 +495,7 @@ module Bitcoin
495
495
  :privkey_version => "ef",
496
496
  :default_port => 18333,
497
497
  :max_money => 21_000_000 * COIN,
498
- :dns_seeds => [ "testseed.bitcoin.interesthings.de" ],
498
+ :dns_seeds => [ ],
499
499
  :genesis_hash => "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008",
500
500
  :proof_of_work_limit => 0x1d07fff8,
501
501
  :alert_pubkeys => ["04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"],
@@ -209,8 +209,8 @@ module Bitcoin
209
209
  end
210
210
 
211
211
  # previous transaction that contains the output we want to use.
212
- def prev_out tx
213
- @prev_out = tx
212
+ def prev_out tx, idx = nil
213
+ @prev_out, @prev_out_index = tx, idx
214
214
  end
215
215
 
216
216
  # index of the output in the #prev_out transaction.
@@ -402,7 +402,7 @@ module Bitcoin::Network
402
402
  end
403
403
  end
404
404
  rescue Bitcoin::Validation::ValidationError
405
- @log.warn { "ValiationError storing #{obj[0]} #{obj[1].hash}: #{$!.message}" }
405
+ @log.warn { "ValidationError storing #{obj[0]} #{obj[1].hash}: #{$!.message}" }
406
406
  # File.open("./validation_error_#{obj[0]}_#{obj[1].hash}.bin", "w") {|f|
407
407
  # f.write(obj[1].to_payload) }
408
408
  # EM.stop
@@ -83,6 +83,15 @@ module Bitcoin
83
83
  [(0...size).map{ i, payload = unpack_var_int(payload); i }, payload]
84
84
  end
85
85
 
86
+ def self.unpack_boolean(payload)
87
+ bdata, payload = payload.unpack("Ca*")
88
+ [ (bdata == 0 ? false : true), payload ]
89
+ end
90
+
91
+ def self.pack_boolean(b)
92
+ (b == true) ? [0xFF].pack("C") : [0x00].pack("C")
93
+ end
94
+
86
95
  BINARY = Encoding.find('ASCII-8BIT')
87
96
 
88
97
  def self.pkt(command, payload)
@@ -138,6 +138,10 @@ module Bitcoin
138
138
  h
139
139
  end
140
140
 
141
+ def size
142
+ payload.bytesize
143
+ end
144
+
141
145
  def hextarget
142
146
  Bitcoin.decode_compact_bits(@bits)
143
147
  end
@@ -4,10 +4,15 @@ module Bitcoin
4
4
  module Protocol
5
5
 
6
6
  class Parser
7
+ attr_reader :stats
7
8
 
8
9
  def initialize(handler=nil)
9
10
  @h = handler || Handler.new
10
11
  @buf = ""
12
+ @stats = {
13
+ 'total_packets' => 0,
14
+ 'total_bytes' => 0
15
+ }
11
16
  end
12
17
 
13
18
  def log
@@ -69,6 +74,9 @@ module Bitcoin
69
74
  end
70
75
 
71
76
  def process_pkt(command, payload)
77
+ @stats['total_packets'] += 1
78
+ @stats['total_bytes'] += payload.bytesize
79
+ @stats[command] ? (@stats[command] += 1) : @stats[command] = 1
72
80
  case command
73
81
  when 'tx'; @h.on_tx( Tx.new(payload) )
74
82
  when 'block'; @h.on_block( Block.new(payload) )
@@ -219,6 +219,10 @@ module Bitcoin
219
219
  @validator ||= Bitcoin::Validation::Tx.new(self, store, block)
220
220
  end
221
221
 
222
+ def size
223
+ payload.bytesize
224
+ end
225
+
222
226
  DEFAULT_BLOCK_PRIORITY_SIZE = 27000
223
227
 
224
228
  def minimum_relay_fee; calculate_minimum_fee(allow_free=true, :relay); end
@@ -43,6 +43,10 @@ module Bitcoin
43
43
 
44
44
  alias :parse_payload :parse_data
45
45
 
46
+ def get_script
47
+ @script_cache || Bitcoin::Script.new(@pk_script)
48
+ end
49
+
46
50
  def to_payload
47
51
  [@value].pack("Q") << Protocol.pack_var_int(@pk_script_length) << @pk_script
48
52
  end
@@ -52,7 +56,7 @@ module Bitcoin
52
56
  end
53
57
 
54
58
  def to_hash(options = {})
55
- script = Bitcoin::Script.new(@pk_script)
59
+ script = get_script
56
60
  h = { 'value' => "%.8f" % (@value / 100000000.0),
57
61
  'scriptPubKey' => script.to_string }
58
62
  h["address"] = script.get_address if script.is_hash160? && options[:with_address]
@@ -19,7 +19,8 @@ module Bitcoin
19
19
  :to => "127.0.0.1:8333",
20
20
  :nonce => Bitcoin::Protocol::Uniq,
21
21
  :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
22
- :last_block => 0 # 188617
22
+ :last_block => 0, # 188617
23
+ :relay => true # BIP0037
23
24
  }.merge( opts.reject{|k,v| v == nil } )
24
25
  end
25
26
 
@@ -30,7 +31,8 @@ module Bitcoin
30
31
  pack_address_field(@fields[:to]),
31
32
  @fields.values_at(:nonce).pack("Q"),
32
33
  Protocol.pack_var_string(@fields[:user_agent]),
33
- @fields.values_at(:last_block).pack("V")
34
+ @fields.values_at(:last_block).pack("V"),
35
+ Protocol.pack_boolean(@fields[:relay]) # Satoshi 0.8.6 doesn't send this but it does respect it
34
36
  ].join
35
37
  end
36
38
 
@@ -42,12 +44,13 @@ module Bitcoin
42
44
  version, services, timestamp, to, from, nonce, payload = payload.unpack("VQQa26a26Qa*")
43
45
  to, from = unpack_address_field(to), unpack_address_field(from)
44
46
  user_agent, payload = Protocol.unpack_var_string(payload)
45
- last_block = payload.unpack("V")[0]
47
+ last_block, payload = payload.unpack("Va*")
48
+ relay, payload = unpack_relay_field(version, payload)
46
49
 
47
50
  @fields = {
48
51
  :version => version, :services => services, :time => timestamp,
49
52
  :from => from, :to => to, :nonce => nonce,
50
- :user_agent => user_agent.to_s, :last_block => last_block
53
+ :user_agent => user_agent.to_s, :last_block => last_block, :relay => relay
51
54
  }
52
55
  self
53
56
  end
@@ -66,6 +69,11 @@ module Bitcoin
66
69
  [[1].pack("Q"), "\x00"*10, "\xFF\xFF", host, port].join
67
70
  end
68
71
 
72
+ # BIP0037: this field starts with version 70001 and is allowed to be missing, defaults to true
73
+ def unpack_relay_field(version, payload)
74
+ ( version >= 70001 and payload ) ? Protocol.unpack_boolean(payload) : [ true, nil ]
75
+ end
76
+
69
77
  def uptime
70
78
  @fields[:time] - Time.now.tv_sec
71
79
  end
@@ -100,6 +100,7 @@ class Bitcoin::Script
100
100
  OP_LSHIFT = 152
101
101
  OP_RSHIFT = 153
102
102
 
103
+ OP_INVALIDOPCODE = 0xff
103
104
 
104
105
  OPCODES = Hash[*constants.grep(/^OP_/).map{|i| [const_get(i), i.to_s] }.flatten]
105
106
  OPCODES[0] = "0"
@@ -447,7 +448,7 @@ class Bitcoin::Script
447
448
 
448
449
  # is this a multisig tx
449
450
  def is_multisig?
450
- return false if @chunks.size > 6 || @chunks.size < 4
451
+ return false if @chunks.size > 6 || @chunks.size < 4 || !@chunks[-2].is_a?(Fixnum)
451
452
  @chunks[-1] == OP_CHECKMULTISIG and get_multisig_pubkeys.all?{|c| c.is_a?(String) }
452
453
  end
453
454
 
@@ -475,6 +476,7 @@ class Bitcoin::Script
475
476
  # get the hash160 for this hash160 or pubkey script
476
477
  def get_hash160
477
478
  return @chunks[2..-3][0].unpack("H*")[0] if is_hash160?
479
+ return @chunks[-2].unpack("H*")[0] if is_p2sh?
478
480
  return Bitcoin.hash160(get_pubkey) if is_pubkey?
479
481
  end
480
482
 
@@ -498,11 +500,16 @@ class Bitcoin::Script
498
500
  }.compact
499
501
  end
500
502
 
503
+ def get_p2sh_address
504
+ Bitcoin.hash160_to_p2sh_address(get_hash160)
505
+ end
506
+
501
507
  # get all addresses this script corresponds to (if possible)
502
508
  def get_addresses
503
509
  return [get_pubkey_address] if is_pubkey?
504
510
  return [get_hash160_address] if is_hash160?
505
511
  return get_multisig_addresses if is_multisig?
512
+ return [get_p2sh_address] if is_p2sh?
506
513
  []
507
514
  end
508
515
 
@@ -102,7 +102,8 @@ module Bitcoin::Storage::Backends
102
102
 
103
103
  def wrap_block(block)
104
104
  return nil unless block
105
- data = {:id => @blk.index(block), :depth => @blk.index(block), :work => @blk.index(block), :chain => 0}
105
+ data = { id: @blk.index(block), depth: @blk.index(block),
106
+ work: @blk.index(block), chain: MAIN, size: block.size }
106
107
  blk = Bitcoin::Storage::Models::Block.new(self, data)
107
108
  [:ver, :prev_block, :mrkl_root, :time, :bits, :nonce].each do |attr|
108
109
  blk.send("#{attr}=", block.send(attr))
@@ -117,7 +118,8 @@ module Bitcoin::Storage::Backends
117
118
  def wrap_tx(transaction)
118
119
  return nil unless transaction
119
120
  blk = @blk.find{|b| b.tx.include?(transaction)}
120
- data = {:id => transaction.hash, :blk_id => @blk.index(blk)}
121
+ data = { id: transaction.hash, blk_id: @blk.index(blk),
122
+ size: transaction.size }
121
123
  tx = Bitcoin::Storage::Models::Tx.new(self, data)
122
124
  tx.ver = transaction.ver
123
125
  tx.lock_time = transaction.lock_time
@@ -130,7 +132,7 @@ module Bitcoin::Storage::Backends
130
132
  def wrap_txin(input)
131
133
  return nil unless input
132
134
  tx = @tx.values.find{|t| t.in.include?(input)}
133
- data = {:tx_id => tx.hash, :tx_idx => tx.in.index(input)}
135
+ data = { tx_id: tx.hash, tx_idx: tx.in.index(input)}
134
136
  txin = Bitcoin::Storage::Models::TxIn.new(self, data)
135
137
  [:prev_out, :prev_out_index, :script_sig_length, :script_sig, :sequence].each do |attr|
136
138
  txin.send("#{attr}=", input.send(attr))
@@ -141,8 +143,8 @@ module Bitcoin::Storage::Backends
141
143
  def wrap_txout(output)
142
144
  return nil unless output
143
145
  tx = @tx.values.find{|t| t.out.include?(output)}
144
- data = {:tx_id => tx.hash, :tx_idx => tx.out.index(output),
145
- :hash160 => Bitcoin::Script.new(output.pk_script).get_hash160 }
146
+ data = {tx_id: tx.hash, tx_idx: tx.out.index(output),
147
+ hash160: Bitcoin::Script.new(output.pk_script).get_hash160 }
146
148
  txout = Bitcoin::Storage::Models::TxOut.new(self, data)
147
149
  [:value, :pk_script_length, :pk_script].each do |attr|
148
150
  txout.send("#{attr}=", output.send(attr))
@@ -14,7 +14,7 @@ module Bitcoin::Storage::Models
14
14
  class Block < Bitcoin::Protocol::Block
15
15
 
16
16
  attr_accessor :ver, :prev_block, :mrkl_root, :time, :bits, :nonce, :tx
17
- attr_reader :store, :id, :depth, :chain, :work
17
+ attr_reader :store, :id, :depth, :chain, :work, :size
18
18
 
19
19
  def initialize store, data
20
20
  @store = store
@@ -22,6 +22,7 @@ module Bitcoin::Storage::Models
22
22
  @depth = data[:depth]
23
23
  @chain = data[:chain]
24
24
  @work = data[:work]
25
+ @size = data[:size]
25
26
  @tx = []
26
27
  end
27
28
 
@@ -35,25 +36,38 @@ module Bitcoin::Storage::Models
35
36
  @store.get_block_by_prev_hash(@hash)
36
37
  end
37
38
 
39
+ def total_out
40
+ @total_out ||= tx.inject(0){ |m,t| m + t.total_out }
41
+ end
42
+
43
+ def total_in
44
+ @total_in ||= tx.inject(0){ |m,t| m + t.total_in }
45
+ end
46
+
47
+ def total_fee
48
+ @total_fee ||= tx.inject(0){ |m,t| m + t.fee }
49
+ end
38
50
  end
39
51
 
40
52
  # Transaction retrieved from storage. (see Bitcoin::Protocol::Tx)
41
53
  class Tx < Bitcoin::Protocol::Tx
42
54
 
43
55
  attr_accessor :ver, :lock_time, :hash
44
- attr_reader :store, :id, :blk_id
56
+ attr_reader :store, :id, :blk_id, :size, :idx
45
57
 
46
58
  def initialize store, data
47
59
  @store = store
48
60
  @id = data[:id]
49
61
  @blk_id = data[:blk_id]
62
+ @size = data[:size]
63
+ @idx = data[:idx]
50
64
  super(nil)
51
65
  end
52
66
 
53
67
  # get the block this transaction is in
54
68
  def get_block
55
69
  return nil unless @blk_id
56
- @store.get_block_by_id(@blk_id)
70
+ @block ||= @store.get_block_by_id(@blk_id)
57
71
  end
58
72
 
59
73
  # get the number of blocks that confirm this tx in the main chain
@@ -61,6 +75,21 @@ module Bitcoin::Storage::Models
61
75
  return 0 unless get_block
62
76
  @store.get_head.depth - get_block.depth + 1
63
77
  end
78
+
79
+ def total_out
80
+ @total_out ||= self.out.inject(0){ |e, o| e + o.value }
81
+ end
82
+
83
+ # if tx_in is coinbase, set in value as total_out, fee could be 0
84
+ def total_in
85
+ @total_in ||= self.in.inject(0){ |m, input|
86
+ m + (input.coinbase? ? total_out : (input.get_prev_out.try(:value) || 0))
87
+ }
88
+ end
89
+
90
+ def fee
91
+ @fee ||= total_in - total_out
92
+ end
64
93
  end
65
94
 
66
95
  # Transaction input retrieved from storage. (see Bitcoin::Protocol::TxIn
@@ -77,15 +106,16 @@ module Bitcoin::Storage::Models
77
106
 
78
107
  # get the transaction this input is in
79
108
  def get_tx
80
-
81
- @store.get_tx_by_id(@tx_id)
109
+ @tx ||= @store.get_tx_by_id(@tx_id)
82
110
  end
83
111
 
84
112
  # get the previous output referenced by this input
85
113
  def get_prev_out
86
- prev_tx = @store.get_tx(@prev_out.reverse_hth)
87
- return nil unless prev_tx
88
- prev_tx.out[@prev_out_index]
114
+ @prev_tx_out ||= begin
115
+ prev_tx = @store.get_tx(@prev_out.reverse_hth)
116
+ return nil unless prev_tx
117
+ prev_tx.out[@prev_out_index]
118
+ end
89
119
  end
90
120
 
91
121
  end
@@ -109,7 +139,7 @@ module Bitcoin::Storage::Models
109
139
 
110
140
  # get the transaction this output is in
111
141
  def get_tx
112
- @store.get_tx_by_id(@tx_id)
142
+ @tx ||= @store.get_tx_by_id(@tx_id)
113
143
  end
114
144
 
115
145
  # get the next input that references this output
@@ -77,7 +77,7 @@ module Bitcoin::Storage::Backends
77
77
  txout_ids = @db[:txout].insert_multiple(new_tx.map.with_index {|tx, tx_idx|
78
78
  tx, _ = *tx
79
79
  tx.out.map.with_index {|txout, txout_idx|
80
- script_type, a, n = *parse_script(txout, txout_i)
80
+ script_type, a, n = *parse_script(txout, txout_i, tx.hash)
81
81
  addrs += a; names += n; txout_i += 1
82
82
  txout_data(new_tx_ids[tx_idx], txout, txout_idx, script_type) } }.flatten)
83
83
 
@@ -107,7 +107,7 @@ module Bitcoin::Storage::Backends
107
107
  end
108
108
 
109
109
  # parse script and collect address/txout mappings to index
110
- def parse_script txout, i
110
+ def parse_script txout, i, tx_hash = ""
111
111
  addrs, names = [], []
112
112
 
113
113
  script = Bitcoin::Script.new(txout.pk_script) rescue nil
@@ -122,11 +122,12 @@ module Bitcoin::Storage::Backends
122
122
  addrs << [i, script.get_hash160]
123
123
  names << [i, script]
124
124
  else
125
- log.warn { "Unknown script type"}# #{tx.hash}:#{txout_idx}" }
125
+ log.info { "Unknown script type in #{tx_hash}:#{i}" }
126
+ log.debug { script.to_string }
126
127
  end
127
128
  script_type = SCRIPT_TYPES.index(script.type)
128
129
  else
129
- log.error { "Error parsing script"}# #{tx.hash}:#{txout_idx}" }
130
+ log.error { "Error parsing script #{tx_hash}:#{i}" }
130
131
  script_type = SCRIPT_TYPES.index(:unknown)
131
132
  end
132
133
  [script_type, addrs, names]
@@ -170,7 +171,7 @@ module Bitcoin::Storage::Backends
170
171
  return transaction[:id] if transaction
171
172
  tx_id = @db[:tx].insert(tx_data(tx))
172
173
  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.out.each_with_index {|o, idx| store_txout(tx_id, o, idx, tx.hash)}
174
175
  tx_id
175
176
  end
176
177
  end
@@ -197,8 +198,8 @@ module Bitcoin::Storage::Backends
197
198
  end
198
199
 
199
200
  # store output +txout+
200
- def store_txout(tx_id, txout, idx)
201
- script_type, addrs, names = *parse_script(txout, idx)
201
+ def store_txout(tx_id, txout, idx, tx_hash = "")
202
+ script_type, addrs, names = *parse_script(txout, idx, tx_hash)
202
203
  txout_id = @db[:txout].insert(txout_data(tx_id, txout, idx, script_type))
203
204
  persist_addrs addrs.map {|i, h| [txout_id, h] }
204
205
  names.each {|i, script| store_name(script, txout_id) }
@@ -345,7 +346,7 @@ module Bitcoin::Storage::Backends
345
346
  def wrap_block(block)
346
347
  return nil unless block
347
348
 
348
- data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain], :work => block[:work].to_i, :hash => block[:hash].hth}
349
+ data = {:id => block[:id], :depth => block[:depth], :chain => block[:chain], :work => block[:work].to_i, :hash => block[:hash].hth, :size => block[:blk_size]}
349
350
  blk = Bitcoin::Storage::Models::Block.new(self, data)
350
351
 
351
352
  blk.ver = block[:version]
@@ -371,7 +372,7 @@ module Bitcoin::Storage::Backends
371
372
  block_id ||= @db[:blk_tx].join(:blk, id: :blk_id)
372
373
  .where(tx_id: transaction[:id], chain: 0).first[:blk_id] rescue nil
373
374
 
374
- data = {id: transaction[:id], blk_id: block_id}
375
+ data = {id: transaction[:id], blk_id: block_id, size: transaction[:tx_size], idx: transaction[:idx]}
375
376
  tx = Bitcoin::Storage::Models::Tx.new(self, data)
376
377
 
377
378
  inputs = db[:txin].filter(:tx_id => transaction[:id]).order(:tx_idx)
@@ -431,6 +432,22 @@ module Bitcoin::Storage::Backends
431
432
  log.info { "Last #{count} blocks are consistent." }
432
433
  end
433
434
 
435
+ # get total received of +address+ address
436
+ def get_received(address)
437
+ return 0 unless Bitcoin.valid_address?(address)
438
+
439
+ txouts = get_txouts_for_address(address)
440
+ return 0 unless txouts.any?
441
+
442
+ txouts.inject(0){ |m, out| m + out.value }
443
+
444
+ # total = 0
445
+ # txouts.each do |txout|
446
+ # tx = txout.get_tx
447
+ # total += txout.value
448
+ # end
449
+ end
450
+
434
451
  end
435
452
 
436
453
  end
@@ -181,7 +181,7 @@ module Bitcoin::Storage
181
181
  else
182
182
  depth = prev_block ? prev_block.depth + 1 : 0
183
183
  log.debug { "=> orphan (#{depth})" }
184
- return [0, 2] unless in_sync?
184
+ return [0, 2] unless (in_sync? || Bitcoin.network_name =~ /testnet/)
185
185
  return persist_block(blk, ORPHAN, depth)
186
186
  end
187
187
  end
@@ -419,7 +419,12 @@ module Bitcoin::Storage
419
419
  File.open(filename) do |file|
420
420
  until file.eof?
421
421
  magic = file.read(4)
422
- raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic
422
+
423
+ # bitcoind pads the ends of the block files so that it doesn't
424
+ # have to reallocate space on every new block.
425
+ break if magic == "\0\0\0\0"
426
+ raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic
427
+
423
428
  size = file.read(4).unpack("L")[0]
424
429
  blk = Bitcoin::P::Block.new(file.read(size))
425
430
  depth, chain = new_block(blk)
@@ -0,0 +1,14 @@
1
+ Sequel.migration do
2
+ up do
3
+ @log.info { "Running migration #{__FILE__}" }
4
+
5
+ alter_table :utxo do
6
+ # This is used when deleting spent uxto rows
7
+ add_index([:tx_hash, :tx_idx])
8
+
9
+ # These don't seem to be necessary
10
+ drop_index :tx_idx
11
+ drop_index :value
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -72,6 +72,17 @@ describe "Bitcoin::Builder" do
72
72
  script.get_address.should == @keys[1].addr
73
73
 
74
74
  tx.verify_input_signature(0, block.tx[0]).should == true
75
+
76
+
77
+ # check shortcuts also work
78
+ tx2 = build_tx do |t|
79
+ t.input {|i| i.prev_out @block.tx[0], 0; i.signature_key @keys[0] }
80
+ t.output {|o| o.value 123; o.script {|s| s.recipient @keys[1].addr } }
81
+ end
82
+ tx2.in[0].prev_out.should == tx.in[0].prev_out
83
+ tx2.in[0].prev_out_index.should == tx.in[0].prev_out_index
84
+ tx2.out[0].value.should == tx.out[0].value
85
+ tx2.out[0].pk_script.should == tx.out[0].pk_script
75
86
  end
76
87
 
77
88
  it "should build unsigned transactions and add the signature hash" do
@@ -27,7 +27,8 @@ describe 'Bitcoin::Protocol::Parser (version)' do
27
27
  :to => "127.0.0.1:57802",
28
28
  :nonce => 12576309328653329813,
29
29
  :user_agent => "/bitcoin-qt:0.6.0/",
30
- :last_block => 46722
30
+ :last_block => 46722,
31
+ :relay => true
31
32
  }
32
33
 
33
34
  pkt = [
@@ -47,7 +48,8 @@ describe 'Bitcoin::Protocol::Parser (version)' do
47
48
  :to => "127.0.0.1:1234",
48
49
  :nonce => 8210299263586646091,
49
50
  :user_agent => '',
50
- :last_block => 250
51
+ :last_block => 250,
52
+ :relay => true
51
53
  }
52
54
  end
53
55
 
@@ -72,7 +74,36 @@ describe 'Bitcoin::Protocol::Parser (version)' do
72
74
  :from => "127.0.0.1:1234",
73
75
  :nonce => 123,
74
76
  :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
75
- :last_block => 188617
77
+ :last_block => 188617,
78
+ :relay => true
79
+ }
80
+ end
81
+
82
+ # check that we support sending and receiving of the BIP0037 fRelay flag
83
+ it 'creates spv enabled version packets' do
84
+ version = Bitcoin::Protocol::Version.new({
85
+ :time => 1337,
86
+ :from => "127.0.0.1:8333",
87
+ :to => "127.0.0.1:1234",
88
+ :nonce => 123,
89
+ :last_block => 188617,
90
+ :relay => false
91
+ })
92
+
93
+ parser = Bitcoin::Protocol::Parser.new( handler = Version_Handler.new )
94
+ parser.parse( version.to_pkt )
95
+
96
+ pkt = handler.pkt
97
+ pkt.fields.should == {
98
+ :version => Bitcoin.network[:protocol_version],
99
+ :services => Bitcoin::Protocol::Version::NODE_NETWORK,
100
+ :time => 1337,
101
+ :to => "127.0.0.1:8333",
102
+ :from => "127.0.0.1:1234",
103
+ :nonce => 123,
104
+ :user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
105
+ :last_block => 188617,
106
+ :relay => false
76
107
  }
77
108
  end
78
109
 
@@ -12,6 +12,7 @@ describe 'Bitcoin::Script' do
12
12
  "76a91417977bca1b6287a5e6559c57ef4b6525e9d7ded688ac",
13
13
  "524104573b6e9f3a714440048a7b87d606bcbf9e45b8586e70a67a3665ea720c095658471a523e5d923f3f3e015626e7c900bd08560ddffeb17d33c5b52c96edb875954104039c2f4e413a26901e67ad4adbb6a4759af87bc16c7120459ecc9482fed3dd4a4502947f7b4c7782dcadc2bed513ed14d5e770452b97ae246ac2030f13b80a5141048b0f9d04e495c3c754f8c3c109196d713d0778882ef098f785570ee6043f8c192d8f84df43ebafbcc168f5d95a074dc4010b62c003e560abc163c312966b74b653ae", # multisig 2 of 3
14
14
  "5141040ee607b584b36e995f2e96dec35457dbb40845d0ce0782c84002134e816a6b8cbc65e9eed047ae05e10760e4113f690fd49ad73b86b04a1d7813d843f8690ace4104220a78f5f6741bb0739675c2cc200643516b02cfdfda5cba21edeaa62c0f954936b30dfd956e3e99af0a8e7665cff6ac5b429c54c418184c81fbcd4bde4088f552ae", # multisig 1 of 2
15
+ "a9149471864495192e39f5f74574b6c8c513588a820487", # p2sh
15
16
  ].map{|s|[s].pack("H*")}
16
17
  PUBKEYS = [
17
18
  "04fb0123fe2c399981bc77d522e2ae3268d2ab15e9a84ae49338a4b1db3886a1ea04cdab955d81e9fa1fcb0c062cb9a5af1ad5dd5064f4afcca322402b07030ec2",
@@ -83,12 +84,12 @@ describe 'Bitcoin::Script' do
83
84
 
84
85
  Bitcoin::Script.from_string("(opcode-230) 4 1 2").to_string.should == "(opcode-230) 4 1 2"
85
86
  Bitcoin::Script.from_string("(opcode 230) 4 1 2").to_string.should == "(opcode-230) 4 1 2"
86
- Bitcoin::Script.from_string("(opcode-65449) 4 1 2").to_string.should == "(opcode-255) OP_HASH160 4 1 2"
87
+ Bitcoin::Script.from_string("(opcode-65449) 4 1 2").to_string.should == "OP_INVALIDOPCODE OP_HASH160 4 1 2"
87
88
 
88
89
  # found in testnet3 block 0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab transactions
89
- Script.new("\xff\xff\xff\xff").to_string.should == "(opcode-255) (opcode-255) (opcode-255) (opcode-255)"
90
+ Script.new("\xff\xff\xff\xff").to_string.should == "OP_INVALIDOPCODE OP_INVALIDOPCODE OP_INVALIDOPCODE OP_INVALIDOPCODE"
90
91
  Script.from_string(Script.new("\xff\xff\xff\xff").to_string).raw.should == "\xFF\xFF\xFF\xFF"
91
- Script.new("\xff\xff\xff").to_string.should == "(opcode-255) (opcode-255) (opcode-255)"
92
+ Script.new("\xff\xff\xff").to_string.should == "OP_INVALIDOPCODE OP_INVALIDOPCODE OP_INVALIDOPCODE"
92
93
  Script.from_string(Script.new("\xff\xff\xff").to_string).raw.should == "\xFF\xFF\xFF"
93
94
  end
94
95
 
@@ -172,6 +173,11 @@ describe 'Bitcoin::Script' do
172
173
  Bitcoin::Script.from_string(output).get_multisig_addresses.should == ["1NdB761LmTmrJixxp93nz7pEiCx5cKPW44"]
173
174
  end
174
175
 
176
+ it "#get_p2sh_address" do
177
+ Script.new(SCRIPT[5]).get_p2sh_address.should ==
178
+ "3FDuvkgzsW7LpzL9RBjtjvL7bFXCEeZ7xi"
179
+ end
180
+
175
181
  it "#get_address" do
176
182
  Script.new(SCRIPT[0]).get_address.should ==
177
183
  "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S"
@@ -182,6 +188,8 @@ describe 'Bitcoin::Script' do
182
188
  "1JiaVc3N3U3CwwcLtzNX1Q4eYfeYxVjtuj"
183
189
  Script.new(SCRIPT[4]).get_address.should ==
184
190
  "1F2Nnyn7niMcheiYhkHrkc18aDxEkFowy5"
191
+ Script.new(SCRIPT[5]).get_address.should ==
192
+ "3FDuvkgzsW7LpzL9RBjtjvL7bFXCEeZ7xi"
185
193
  end
186
194
 
187
195
  it "#get_addresses" do
@@ -201,6 +209,7 @@ describe 'Bitcoin::Script' do
201
209
  Script.new(SCRIPT[2]).is_standard?.should == true
202
210
  Script.new(SCRIPT[3]).is_standard?.should == true
203
211
  Script.new(SCRIPT[4]).is_standard?.should == true
212
+ Script.new(SCRIPT[5]).is_standard?.should == true
204
213
  end
205
214
 
206
215
  it '#is_pubkey?' do
@@ -209,6 +218,7 @@ describe 'Bitcoin::Script' do
209
218
  Script.new(SCRIPT[2]).is_pubkey?.should == false
210
219
  Script.new(SCRIPT[3]).is_pubkey?.should == false
211
220
  Script.new(SCRIPT[4]).is_send_to_ip?.should == false
221
+ Script.new(SCRIPT[5]).is_pubkey?.should == false
212
222
  end
213
223
 
214
224
  it "#is_hash160?" do
@@ -217,6 +227,7 @@ describe 'Bitcoin::Script' do
217
227
  Script.new(SCRIPT[2]).is_hash160?.should == true
218
228
  Script.from_string("OP_DUP OP_HASH160 0 OP_EQUALVERIFY OP_CHECKSIG")
219
229
  .is_hash160?.should == false
230
+ Script.new(SCRIPT[5]).is_hash160?.should == false
220
231
  end
221
232
 
222
233
  it "#is_multisig?" do
@@ -226,6 +237,16 @@ describe 'Bitcoin::Script' do
226
237
  Script.new("OP_DUP OP_DROP 2 #{PUBKEYS[0..2].join(' ')} 3 OP_CHECKMULTISIG")
227
238
  .is_multisig?.should == false
228
239
  Script.new("OP_DROP OP_CHECKMULTISIG").is_multisig?.should == false
240
+ Script.from_string("d366fb5cbf048801b1bf0742bb0d873f65afb406f41756bd4a31865870f6a928 OP_DROP 2 02aae4b5cd593da83679a9c5cadad4c180c008a40dd3ed240cceb2933b9912da36 03a5aebd8b1b6eec06abc55fb13c72a9ed2143f9eed7d665970e38853d564bf1ab OP_CHECKMULTISIG").is_multisig?.should == false
241
+ end
242
+
243
+ it '#is_p2sh?' do
244
+ Script.new(SCRIPT[0]).is_p2sh?.should == false
245
+ Script.new(SCRIPT[1]).is_p2sh?.should == false
246
+ Script.new(SCRIPT[2]).is_p2sh?.should == false
247
+ Script.new(SCRIPT[3]).is_p2sh?.should == false
248
+ Script.new(SCRIPT[4]).is_p2sh?.should == false
249
+ Script.new(SCRIPT[5]).is_p2sh?.should == true
229
250
  end
230
251
 
231
252
  it "#type" do
@@ -234,6 +255,7 @@ describe 'Bitcoin::Script' do
234
255
  Script.new(SCRIPT[2]).type.should == :hash160
235
256
  Script.new(SCRIPT[3]).type.should == :multisig
236
257
  Script.new(SCRIPT[4]).type.should == :multisig
258
+ Script.new(SCRIPT[5]).type.should == :p2sh
237
259
  end
238
260
 
239
261
  end
@@ -330,7 +352,7 @@ describe 'Bitcoin::Script' do
330
352
 
331
353
  # testnet3 tx: 5dea81f9d9d2ea6d06ce23ff225d1e240392519017643f75c96fa2e4316d948a
332
354
  script = Script.new( ["0063bac0d0e0f0f1f2f3f3f4ff675168"].pack("H*") )
333
- script.to_string.should == "0 OP_IF (opcode-186) (opcode-192) (opcode-208) (opcode-224) (opcode-240) (opcode-241) (opcode-242) (opcode-243) (opcode-243) (opcode-244) (opcode-255) OP_ELSE 1 OP_ENDIF"
355
+ script.to_string.should == "0 OP_IF (opcode-186) (opcode-192) (opcode-208) (opcode-224) (opcode-240) (opcode-241) (opcode-242) (opcode-243) (opcode-243) (opcode-244) OP_INVALIDOPCODE OP_ELSE 1 OP_ENDIF"
334
356
  script.run.should == true
335
357
 
336
358
  # mainnet tx: 61a078472543e9de9247446076320499c108b52307d8d0fafbe53b5c4e32acc4 redeeming output from 5342c96b946ea2c5e497de5dbf7762021f94aba2c8222c17ed28492fdbb4a6d9
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoin-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-12-10 00:00:00.000000000 Z
12
+ date: 2014-01-23 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: This is a ruby library for interacting with the bitcoin protocol/network
15
15
  email:
@@ -98,6 +98,7 @@ files:
98
98
  - lib/bitcoin/storage/storage.rb
99
99
  - lib/bitcoin/storage/utxo/migrations/001_base_schema.rb
100
100
  - lib/bitcoin/storage/utxo/migrations/002_utxo.rb
101
+ - lib/bitcoin/storage/utxo/migrations/003_update_indices.rb
101
102
  - lib/bitcoin/storage/utxo/utxo_store.rb
102
103
  - lib/bitcoin/validation.rb
103
104
  - lib/bitcoin/version.rb