bitcoin-ruby 0.0.4 → 0.0.5

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