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