bitcoin-ruby 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
@@ -0,0 +1,369 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
module Bitcoin::Validation
|
4
|
+
|
5
|
+
# maximum size of a block (in bytes)
|
6
|
+
MAX_BLOCK_SIZE = 1_000_000
|
7
|
+
|
8
|
+
# maximum number of signature operations in a block
|
9
|
+
MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50
|
10
|
+
|
11
|
+
# maximum integer value
|
12
|
+
INT_MAX = 0xffffffff
|
13
|
+
|
14
|
+
# number of confirmations required before coinbase tx can be spent
|
15
|
+
COINBASE_MATURITY = 100
|
16
|
+
|
17
|
+
# interval (in blocks) for difficulty retarget
|
18
|
+
RETARGET = 2016
|
19
|
+
|
20
|
+
# interval (in blocks) for mining reward reduction
|
21
|
+
REWARD_DROP = 210_000
|
22
|
+
|
23
|
+
class ValidationError < StandardError
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
class Block
|
28
|
+
attr_accessor :block, :store, :prev_block, :error
|
29
|
+
|
30
|
+
RULES = {
|
31
|
+
syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :mrkl_root, :transactions_syntax],
|
32
|
+
context: [:prev_hash, :difficulty, :coinbase_value, :min_timestamp, :transactions_context]
|
33
|
+
}
|
34
|
+
|
35
|
+
# TODO merged mining validations
|
36
|
+
if Bitcoin.namecoin?
|
37
|
+
RULES[:syntax] -= [:bits, :coinbase, :coinbase_scriptsig, :mrkl_root]
|
38
|
+
RULES[:context] -= [:difficulty, :coinbase_value]
|
39
|
+
end
|
40
|
+
|
41
|
+
# validate block rules. +opts+ are:
|
42
|
+
# rules:: which rulesets to validate (default: [:syntax, :context])
|
43
|
+
# raise_errors:: whether to raise ValidationError on failure (default: false)
|
44
|
+
def validate(opts = {})
|
45
|
+
return true if KNOWN_EXCEPTIONS.include?(block.hash)
|
46
|
+
opts[:rules] ||= [:syntax, :context]
|
47
|
+
opts[:rules].each do |name|
|
48
|
+
store.log.debug { "validating block #{name} #{block.hash} (#{block.to_payload.bytesize} bytes)" }
|
49
|
+
RULES[name].each.with_index do |rule, i|
|
50
|
+
unless (res = send(rule)) && res == true
|
51
|
+
raise ValidationError, "block error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
|
52
|
+
@error = [rule, res]
|
53
|
+
return false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
# setup new validator for given +block+, validating context with +store+,
|
61
|
+
# optionally passing the +prev_block+ for optimization.
|
62
|
+
def initialize block, store, prev_block = nil
|
63
|
+
@block, @store, @error = block, store, nil
|
64
|
+
@prev_block = prev_block || store.get_block(block.prev_block.reverse_hth)
|
65
|
+
end
|
66
|
+
|
67
|
+
# check that block hash matches header
|
68
|
+
def hash
|
69
|
+
claimed = block.hash; real = block.recalc_block_hash
|
70
|
+
claimed == real || [claimed, real]
|
71
|
+
end
|
72
|
+
|
73
|
+
# check that block has at least one tx (the coinbase)
|
74
|
+
def tx_list
|
75
|
+
block.tx.any? || block.tx.size
|
76
|
+
end
|
77
|
+
|
78
|
+
# check that block hash matches claimed bits
|
79
|
+
def bits
|
80
|
+
actual = block.hash.to_i(16)
|
81
|
+
expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
|
82
|
+
actual <= expected || [actual, expected]
|
83
|
+
end
|
84
|
+
|
85
|
+
# check that block time is not greater than max
|
86
|
+
def max_timestamp
|
87
|
+
time, max = block.time, Time.now.to_i + 2*60*60
|
88
|
+
time < max || [time, max]
|
89
|
+
end
|
90
|
+
|
91
|
+
# check that coinbase is present
|
92
|
+
def coinbase
|
93
|
+
coinbase, *rest = block.tx.map{|t| t.inputs.size == 1 && t.inputs.first.coinbase? }
|
94
|
+
(coinbase && rest.none?) || [coinbase ? 1 : 0, rest.select{|r| r}.size]
|
95
|
+
end
|
96
|
+
|
97
|
+
# check that coinbase scriptsig is valid
|
98
|
+
def coinbase_scriptsig
|
99
|
+
size = block.tx.first.in.first.script_sig.bytesize
|
100
|
+
size.between?(2,100) || [size, 2, 100]
|
101
|
+
end
|
102
|
+
|
103
|
+
# check that coinbase value is valid; no more than reward + fees
|
104
|
+
def coinbase_value
|
105
|
+
reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
|
106
|
+
fees = 0
|
107
|
+
block.tx[1..-1].map.with_index do |t, idx|
|
108
|
+
val = tx_validators[idx]
|
109
|
+
fees += t.in.map.with_index {|i, idx|
|
110
|
+
val.prev_txs[idx].out[i.prev_out_index].value rescue 0
|
111
|
+
}.inject(:+)
|
112
|
+
val.clear_cache # memory optimization on large coinbases, see testnet3 block 4110
|
113
|
+
end
|
114
|
+
coinbase_output = block.tx[0].out.map(&:value).inject(:+)
|
115
|
+
coinbase_output <= reward + fees || [coinbase_output, reward, fees]
|
116
|
+
end
|
117
|
+
|
118
|
+
# check that merkle root matches transaction hashes
|
119
|
+
def mrkl_root
|
120
|
+
actual, expected = block.mrkl_root.reverse_hth, Bitcoin.hash_mrkl_tree(block.tx.map(&:hash))[-1]
|
121
|
+
actual == expected || [actual, expected]
|
122
|
+
end
|
123
|
+
|
124
|
+
def prev_hash
|
125
|
+
@prev_block && @prev_block.hash == block.prev_block.reverse_hth
|
126
|
+
end
|
127
|
+
|
128
|
+
# check that bits satisfy required difficulty
|
129
|
+
def difficulty
|
130
|
+
return true if Bitcoin.network_name == :testnet3
|
131
|
+
block.bits == next_bits_required || [block.bits, next_bits_required]
|
132
|
+
end
|
133
|
+
|
134
|
+
# check that timestamp is newer than the median of the last 11 blocks
|
135
|
+
def min_timestamp
|
136
|
+
return true if store.get_depth <= 11
|
137
|
+
d = store.get_depth
|
138
|
+
first = store.db[:blk][hash: block.prev_block.reverse.blob]
|
139
|
+
times = [first[:time]]
|
140
|
+
(10).times { first = store.db[:blk][hash: first[:prev_hash].blob]
|
141
|
+
times << first[:time] }
|
142
|
+
times.sort!
|
143
|
+
mid, rem = times.size.divmod(2)
|
144
|
+
min_time = (rem == 0 ? times[mid-1, 2].inject(:+) / 2.0 : times[mid])
|
145
|
+
|
146
|
+
block.time > min_time || [block.time, min_time]
|
147
|
+
end
|
148
|
+
|
149
|
+
# check transactions
|
150
|
+
def transactions_syntax
|
151
|
+
tx_validators.all?{|v|
|
152
|
+
begin
|
153
|
+
v.validate(rules: [:syntax], raise_errors: true)
|
154
|
+
rescue ValidationError
|
155
|
+
store.log.info { $!.message }
|
156
|
+
return false
|
157
|
+
end
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
def transactions_context
|
162
|
+
tx_validators.all?{|v|
|
163
|
+
begin
|
164
|
+
v.validate(rules: [:context], raise_errors: true)
|
165
|
+
rescue ValidationError
|
166
|
+
store.log.info { $!.message }
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
}
|
170
|
+
end
|
171
|
+
|
172
|
+
def tx_validators
|
173
|
+
@tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
|
174
|
+
end
|
175
|
+
|
176
|
+
def next_bits_required
|
177
|
+
index = (prev_block.depth + 1) / RETARGET
|
178
|
+
max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
|
179
|
+
return Bitcoin.network[:proof_of_work_limit] if index == 0
|
180
|
+
return prev_block.bits if (prev_block.depth + 1) % RETARGET != 0
|
181
|
+
last = store.db[:blk][hash: prev_block.hash.htb.blob]
|
182
|
+
first = store.db[:blk][hash: last[:prev_hash].blob]
|
183
|
+
(RETARGET-2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
|
184
|
+
|
185
|
+
nActualTimespan = last[:time] - first[:time]
|
186
|
+
nTargetTimespan = RETARGET * 600
|
187
|
+
|
188
|
+
nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
|
189
|
+
nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
|
190
|
+
|
191
|
+
target = Bitcoin.decode_compact_bits(last[:bits]).to_i(16)
|
192
|
+
new_target = [max_target, (target * nActualTimespan)/nTargetTimespan].min
|
193
|
+
Bitcoin.encode_compact_bits new_target.to_s(16)
|
194
|
+
end
|
195
|
+
|
196
|
+
KNOWN_EXCEPTIONS = [
|
197
|
+
Bitcoin.network[:genesis_hash], # genesis block
|
198
|
+
"00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec", # BIP30 exception
|
199
|
+
"00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721", # BIP30 exception
|
200
|
+
]
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
class Tx
|
205
|
+
attr_accessor :tx, :store, :error
|
206
|
+
|
207
|
+
RULES = {
|
208
|
+
syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
|
209
|
+
context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
|
210
|
+
}
|
211
|
+
|
212
|
+
# validate tx rules. +opts+ are:
|
213
|
+
# rules:: which rulesets to validate (default: [:syntax, :context])
|
214
|
+
# raise_errors:: whether to raise ValidationError on failure (default: false)
|
215
|
+
def validate(opts = {})
|
216
|
+
return true if KNOWN_EXCEPTIONS.include?(tx.hash)
|
217
|
+
opts[:rules] ||= [:syntax, :context]
|
218
|
+
opts[:rules].each do |name|
|
219
|
+
store.log.debug { "validating tx #{name} #{tx.hash} (#{tx.to_payload.bytesize} bytes)" } if store
|
220
|
+
RULES[name].each.with_index do |rule, i|
|
221
|
+
unless (res = send(rule)) && res == true
|
222
|
+
raise ValidationError, "tx error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
|
223
|
+
@error = [rule, res]
|
224
|
+
return false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
clear_cache # memory optimizatons
|
229
|
+
true
|
230
|
+
end
|
231
|
+
|
232
|
+
KNOWN_EXCEPTIONS = [
|
233
|
+
# p2sh with invalid inner script, accepted by old miner before 4-2012 switchover
|
234
|
+
"6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192",
|
235
|
+
# p2sh with invalid inner script, accepted by old miner before 4-2012 switchover (testnet)
|
236
|
+
"b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
|
237
|
+
]
|
238
|
+
|
239
|
+
# setup new validator for given +tx+, validating context with +store+.
|
240
|
+
# also needs the +block+ to find prev_outs for chains of tx inside one block.
|
241
|
+
def initialize tx, store, block = nil
|
242
|
+
@tx, @store, @block, @errors = tx, store, block, []
|
243
|
+
end
|
244
|
+
|
245
|
+
# check that tx hash matches data
|
246
|
+
def hash
|
247
|
+
generated_hash = tx.generate_hash(tx.to_payload)
|
248
|
+
tx.hash == generated_hash || [tx.hash, generated_hash]
|
249
|
+
end
|
250
|
+
|
251
|
+
# check that tx has at least one input and one output
|
252
|
+
def lists
|
253
|
+
(tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size]
|
254
|
+
end
|
255
|
+
|
256
|
+
# check that tx size doesn't exceed MAX_BLOCK_SIZE.
|
257
|
+
def max_size
|
258
|
+
tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
|
259
|
+
end
|
260
|
+
|
261
|
+
# check that total output value doesn't exceed MAX_MONEY.
|
262
|
+
def output_values
|
263
|
+
total = tx.out.inject(0) {|e, out| e + out.value }
|
264
|
+
total <= Bitcoin::network[:max_money] || [total, Bitcoin::network[:max_money]]
|
265
|
+
end
|
266
|
+
|
267
|
+
# check that none of the inputs is coinbase
|
268
|
+
# (coinbase tx do not get validated)
|
269
|
+
def inputs
|
270
|
+
tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))]
|
271
|
+
end
|
272
|
+
|
273
|
+
# check that lock_time doesn't exceed INT_MAX
|
274
|
+
def lock_time
|
275
|
+
tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
|
276
|
+
end
|
277
|
+
|
278
|
+
# check that min_size is at least 86 bytes
|
279
|
+
# (smaller tx can't be valid / do anything useful)
|
280
|
+
def min_size
|
281
|
+
tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86]
|
282
|
+
end
|
283
|
+
|
284
|
+
# check that tx matches "standard" rules.
|
285
|
+
# this is currently disabled since not all miners enforce it.
|
286
|
+
def standard
|
287
|
+
return true # not enforced by all miners
|
288
|
+
return false unless min_size
|
289
|
+
tx.out.all? {|o| Bitcoin::Script.new(o.pk_script).is_standard? }
|
290
|
+
end
|
291
|
+
|
292
|
+
# check that all prev_outs exist
|
293
|
+
# (and are in a block in the main chain, or the current block; see #prev_txs)
|
294
|
+
def prev_out
|
295
|
+
missing = tx.in.reject.with_index {|txin, idx|
|
296
|
+
prev_txs[idx].out[txin.prev_out_index] rescue false }
|
297
|
+
return true if prev_txs.size == tx.in.size && missing.empty?
|
298
|
+
|
299
|
+
missing.each {|i| store.log.warn { "prev out #{i.prev_out.reverse_hth}:#{i.prev_out_index} missing" } }
|
300
|
+
missing.map {|i| [i.prev_out.reverse_hth, i.prev_out_index] }
|
301
|
+
end
|
302
|
+
|
303
|
+
# TODO: validate coinbase maturity
|
304
|
+
|
305
|
+
# check that all input signatures are valid
|
306
|
+
def signatures
|
307
|
+
sigs = tx.in.map.with_index {|txin, idx| tx.verify_input_signature(idx, prev_txs[idx], (@block ? @block.time : 0)) }
|
308
|
+
sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
|
309
|
+
end
|
310
|
+
|
311
|
+
# check that none of the prev_outs are already spent in the main chain
|
312
|
+
def spent
|
313
|
+
spent = tx.in.map.with_index {|txin, idx|
|
314
|
+
next false if @block && @block.tx.include?(prev_txs[idx])
|
315
|
+
next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
|
316
|
+
next false unless next_tx = next_in.get_tx
|
317
|
+
next false unless next_block = next_tx.get_block
|
318
|
+
next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
|
319
|
+
}
|
320
|
+
spent.none? || spent.map.with_index {|s, i| s ? i : nil }
|
321
|
+
end
|
322
|
+
|
323
|
+
# check that the total input value doesn't exceed MAX_MONEY
|
324
|
+
def input_values
|
325
|
+
total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]]
|
326
|
+
end
|
327
|
+
|
328
|
+
# check that the total output value doesn't exceed the total input value
|
329
|
+
def output_sum
|
330
|
+
total_in >= total_out || [total_out, total_in]
|
331
|
+
end
|
332
|
+
|
333
|
+
# empty prev txs cache
|
334
|
+
def clear_cache
|
335
|
+
@prev_txs = nil
|
336
|
+
@total_in = nil
|
337
|
+
@total_out = nil
|
338
|
+
end
|
339
|
+
|
340
|
+
# collect prev_txs needed to verify the inputs of this tx.
|
341
|
+
# only returns tx that are in a block in the main chain or the current block.
|
342
|
+
def prev_txs
|
343
|
+
@prev_txs ||= tx.in.map {|i|
|
344
|
+
prev_tx = store.get_tx(i.prev_out.reverse_hth)
|
345
|
+
next prev_tx if store.class.name =~ /UtxoStore/ && prev_tx
|
346
|
+
next nil if !prev_tx && !@block
|
347
|
+
|
348
|
+
if store.class.name =~ /SequelStore/
|
349
|
+
block = store.db[:blk][id: prev_tx.blk_id] if prev_tx
|
350
|
+
next prev_tx if block && block[:chain] == 0
|
351
|
+
else
|
352
|
+
next prev_tx if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
|
353
|
+
end
|
354
|
+
next nil if !@block
|
355
|
+
@block.tx.find {|t| t.binary_hash == i.prev_out }
|
356
|
+
}.compact
|
357
|
+
end
|
358
|
+
|
359
|
+
|
360
|
+
def total_in
|
361
|
+
@total_in ||= tx.in.each_with_index.inject(0){|acc,(input,idx)| acc + prev_txs[idx].out[input.prev_out_index].value }
|
362
|
+
end
|
363
|
+
|
364
|
+
def total_out
|
365
|
+
@total_out ||= tx.out.inject(0){|acc,output| acc + output.value }
|
366
|
+
end
|
367
|
+
|
368
|
+
end
|
369
|
+
end
|
data/lib/bitcoin/version.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
module Bitcoin::Wallet
|
2
4
|
|
3
5
|
# select unspent txouts to be used by the Wallet when creating a new transaction
|
@@ -14,6 +16,7 @@ module Bitcoin::Wallet
|
|
14
16
|
@txouts.each do |txout|
|
15
17
|
begin
|
16
18
|
next if txout.get_next_in
|
19
|
+
next if Bitcoin.namecoin? && txout.type.to_s =~ /^name_/
|
17
20
|
next unless txout.get_address
|
18
21
|
next unless txout.get_tx.get_block
|
19
22
|
txouts << txout
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
module Bitcoin::Wallet
|
2
4
|
|
3
5
|
# Deterministic key generator as described in
|
4
6
|
# https://bitcointalk.org/index.php?topic=11665.0.
|
5
|
-
#
|
7
|
+
#
|
6
8
|
# Takes a seed and generates an arbitrary amount of keys.
|
7
9
|
# Protects against brute-force attacks by requiring the
|
8
10
|
# key hash to fit a difficulty target, much like the block chain.
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'stringio'
|
3
5
|
|
@@ -12,6 +14,7 @@ module Bitcoin::Wallet
|
|
12
14
|
# [config] Hash of settings ({:file => "/foo/bar.json"})
|
13
15
|
def initialize config
|
14
16
|
@config = Hash[config.map{|k,v|[k.to_sym,v]}]
|
17
|
+
@config[:file].sub!("~", ENV["HOME"]) if @config[:file].is_a?(String)
|
15
18
|
@keys = []
|
16
19
|
load_keys
|
17
20
|
end
|
@@ -92,9 +95,10 @@ module Bitcoin::Wallet
|
|
92
95
|
def import(base58, label = nil)
|
93
96
|
raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
|
94
97
|
key = Bitcoin::Key.from_base58(base58)
|
98
|
+
raise ArgumentError, "Address #{key.addr} already in use" if label && find_key(key.addr)
|
95
99
|
@keys << {:label => label, :addr => key.addr, :key => key}
|
96
100
|
save_keys
|
97
|
-
key
|
101
|
+
key
|
98
102
|
end
|
99
103
|
|
100
104
|
# Load keys from file.
|
@@ -139,7 +143,7 @@ module Bitcoin::Wallet
|
|
139
143
|
@config[:file].reopen
|
140
144
|
dumper.call(@config[:file])
|
141
145
|
@config[:file].rewind
|
142
|
-
|
146
|
+
else
|
143
147
|
File.open(@config[:file], 'w'){|file| dumper.call(file) }
|
144
148
|
end
|
145
149
|
end
|
data/lib/bitcoin/wallet/txdp.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
class Bitcoin::Wallet::TxDP
|
2
4
|
|
3
5
|
attr_accessor :id, :tx, :inputs
|
@@ -7,7 +9,7 @@ class Bitcoin::Wallet::TxDP
|
|
7
9
|
@inputs = []
|
8
10
|
return unless tx.any?
|
9
11
|
@tx[0].in.each_with_index do |input, i|
|
10
|
-
prev_out_hash = input.prev_out.
|
12
|
+
prev_out_hash = input.prev_out.reverse_hth
|
11
13
|
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
12
14
|
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
13
15
|
prev_out = prev_tx.out[input.prev_out_index]
|
@@ -27,7 +29,7 @@ class Bitcoin::Wallet::TxDP
|
|
27
29
|
def sign_inputs
|
28
30
|
@inputs.each_with_index do |txin, i|
|
29
31
|
input = @tx[0].in[i]
|
30
|
-
prev_out_hash = input.prev_out.
|
32
|
+
prev_out_hash = input.prev_out.reverse_hth
|
31
33
|
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
32
34
|
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
33
35
|
prev_out = prev_tx.out[input.prev_out_index]
|
@@ -47,7 +49,7 @@ class Bitcoin::Wallet::TxDP
|
|
47
49
|
def serialize
|
48
50
|
lines = []
|
49
51
|
lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
|
50
|
-
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").
|
52
|
+
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse_hth
|
51
53
|
lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
|
52
54
|
tx = @tx.map(&:to_payload).join.unpack("H*")[0]
|
53
55
|
tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
@@ -57,7 +59,7 @@ class Bitcoin::Wallet::TxDP
|
|
57
59
|
next unless input[1]
|
58
60
|
input[1].each do |sig|
|
59
61
|
size = [sig[1]].pack("H*").bytesize
|
60
|
-
size = [size].pack("C").ljust(2, "\x00").
|
62
|
+
size = [size].pack("C").ljust(2, "\x00").reverse_hth
|
61
63
|
lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
|
62
64
|
sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
63
65
|
lines << sig_str.strip
|