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 +1 -0
- data/README.rdoc +4 -5
- data/doc/CONFIG.rdoc +1 -1
- data/doc/STORAGE.rdoc +16 -4
- data/lib/bitcoin.rb +1 -1
- data/lib/bitcoin/builder.rb +2 -2
- data/lib/bitcoin/network/node.rb +1 -1
- data/lib/bitcoin/protocol.rb +9 -0
- data/lib/bitcoin/protocol/block.rb +4 -0
- data/lib/bitcoin/protocol/parser.rb +8 -0
- data/lib/bitcoin/protocol/tx.rb +4 -0
- data/lib/bitcoin/protocol/txout.rb +5 -1
- data/lib/bitcoin/protocol/version.rb +12 -4
- data/lib/bitcoin/script.rb +8 -1
- data/lib/bitcoin/storage/dummy/dummy_store.rb +7 -5
- data/lib/bitcoin/storage/models.rb +39 -9
- data/lib/bitcoin/storage/sequel/sequel_store.rb +26 -9
- data/lib/bitcoin/storage/storage.rb +7 -2
- data/lib/bitcoin/storage/utxo/migrations/003_update_indices.rb +14 -0
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/builder_spec.rb +11 -0
- data/spec/bitcoin/protocol/version_spec.rb +34 -3
- data/spec/bitcoin/script/script_spec.rb +26 -4
- metadata +3 -2
data/.gitignore
CHANGED
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
|
210
|
-
|
211
|
-
|
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::
|
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"
|
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
|
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 +
|
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 => [
|
498
|
+
:dns_seeds => [ ],
|
499
499
|
:genesis_hash => "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008",
|
500
500
|
:proof_of_work_limit => 0x1d07fff8,
|
501
501
|
:alert_pubkeys => ["04302390343f91cc401d56d68b123028bf52e5fca1939df127f63c6467cdf9c8e2c14b61104cf817d0b780da337893ecc4aaff1309e536162dabbdb45200ca2b0a"],
|
data/lib/bitcoin/builder.rb
CHANGED
@@ -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.
|
data/lib/bitcoin/network/node.rb
CHANGED
@@ -402,7 +402,7 @@ module Bitcoin::Network
|
|
402
402
|
end
|
403
403
|
end
|
404
404
|
rescue Bitcoin::Validation::ValidationError
|
405
|
-
@log.warn { "
|
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
|
data/lib/bitcoin/protocol.rb
CHANGED
@@ -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)
|
@@ -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) )
|
data/lib/bitcoin/protocol/tx.rb
CHANGED
@@ -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 =
|
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("
|
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
|
data/lib/bitcoin/script.rb
CHANGED
@@ -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 = {:
|
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 = {:
|
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 = {:
|
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 = {:
|
145
|
-
:
|
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
|
-
|
87
|
-
|
88
|
-
|
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.
|
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
|
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
|
-
|
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
|
data/lib/bitcoin/version.rb
CHANGED
@@ -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 == "
|
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 == "
|
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 == "
|
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)
|
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
|
+
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:
|
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
|